IteratorAsync<A> is a functional-wrapper for IAsyncEnumerator<A>. The abstraction leaks a little, so it's worth
understanding how it works by reading the details below. On the whole it behaves like an immutable stream
that caches values as it goes, but there's some footguns that you should be aware of so that they can be
avoided.
Problem: IAsyncEnumerator<A>
- It is mutable which means using it in immutable data-structures is problematic.
- If you pass an
IAsyncEnumeratorreference to two threads, each thread can callMoveNextand it will move the enumeration position for other thread, or even worse, move past the end of the sequence due to race conditions. - Enumerators start before the first item and use a complicated mechanism for accessing and testing the validity of the element value.
Nobody in their right mind would invent an interface like IAsyncEnumerator<A> today.
Solution: IteratorAsync<A>
IteratorAsync<A> still uses IAsyncEnumerator<A> internally, but it makes it thread-safe and functional. From the outside
the type acts and works exactly like any other immutable sequence, but internally it does some quite complex
processing to achieve this with an IAsyncEnumerator<A> reference.
You may say "Why not just drop
IAsyncEnumerator<A>?" - which is a completely valid position to hold. Unfortunately,IAsyncEnumerableandIAsyncEnumeratorare baked into the CPS state-machine that is used foryield returnandyield break. So, we don't get to ignore those types, and instead we need to make them play nice.
IAsyncEnumerable<A> has a method called GetAsyncEnumerator() which is used to access an IAsyncEnumerator<A>. A new extension
method is available called GetIteratorAsync(), this will yield an IteratorAsync<A>.
Contents
- IteratorAsync <A>
- Empty = new Nil()
- Head
- Tail
- IsEmpty
- Count
- Clone ()
- Split ()
- AsEnumerable ([EnumeratorCancellation] CancellationToken token)
- Select <B> (Func<A, B> f)
- Map <B> (Func<A, B> f)
- Bind <B> (Func<A, IteratorAsync<B>> f)
- SelectMany <B, C> (Func<A, IteratorAsync<B>> bind, Func<A, B, C> project)
- Apply <B> (IteratorAsync<Func<A, B>> ff, IteratorAsync<A> fa)
- Concat (IteratorAsync<A> other)
- Fold <S> ( S state, Func<A, Func<S, S>> f)
- Fold <S> ( S state, Func<S, A, S> f)
- FoldWhile <S> ( S state, Func<A, Func<S, S>> f, Func<(S State, A Value), bool> predicate)
- FoldWhile <S> ( S state, Func<S, A, S> f, Func<(S State, A Value), bool> predicate)
- FoldUntil <S> ( S state, Func<A, Func<S, S>> f, Func<(S State, A Value), bool> predicate)
- FoldUntil <S> ( S state, Func<S, A, S> f, Func<(S State, A Value), bool> predicate)
- Merge (IteratorAsync<A> other)
- Zip (IteratorAsync<A> other)
- + (IteratorAsync<A> ma, IteratorAsync<A> mb)
- DisposeAsync ()
- GetAsyncEnumerator (CancellationToken token)
- ToString ()
- Nil
- Cons
- IteratorAsync
Sub modules
| Extensions |
| Trait |
class IteratorAsync <A> Source #
Wrapper for IEnumerator that makes it work like an immutable sequence.
It is thread-safe and impossible for any item in the sequence to be enumerated more than once.
IEnumerator from the .NET BCL has several problems:
- It's very imperative
- It's not thread-safe, two enumerators can't be shared
The lack of support for sharing of enumerators means that it's problematic using it internally
in types like StreamT, or anything that needs to keep an IEnumerator alive for any period
of time.
NOTE: There is a per-item allocation to hold the state of the iterator. These are discarded as
you enumerate the sequence. However, technically it is possible to hold the initial Iterator
value and subsequently gain a cached sequence of every item encountered in the enumerator.
That may well be valuable for circumstances where re-evaluation would be expensive. However,
for infinite-streams this would be extremely problematic. So, make sure you discard any
previous IteratorAsync values as you walk the sequence.
Parameters
| type | A | Item value type |
Fields
field IteratorAsync<A> Empty = new Nil() Source #
Empty iterator
Properties
Methods
method IteratorAsync<A> Clone () Source #
Clone the iterator so that we can consume it without having the head item referenced. This will stop any GC pressure when processing large or infinite sequences.
method IteratorAsync<A> Split () Source #
When iterating a sequence, it is possible (before evaluation of the Tail) to Terminate the current
iterator and to take a new iterator that continues on from the current location. The reasons for doing
this are to break the linked-list chain so that there isn't a big linked-list of objects in memory that
can't be garbage collected.
Any other iterator references that came before this one will terminate at this point. Splitting the previous and subsequent iterators here.
Parameters
| returns | New iterator that starts from the current iterator position. | |
method IAsyncEnumerable<A> AsEnumerable ([EnumeratorCancellation] CancellationToken token) Source #
Create an IEnumerable from an Iterator
method IteratorAsync<C> SelectMany <B, C> (Func<A, IteratorAsync<B>> bind, Func<A, B, C> project) Source #
Monad bind
method IteratorAsync<B> Apply <B> (IteratorAsync<Func<A, B>> ff, IteratorAsync<A> fa) Source #
Applicative apply
method ValueTask<S> Fold <S> ( S state, Func<A, Func<S, S>> f) Source #
Fold the sequence while there are more items remaining
method ValueTask<S> Fold <S> ( S state, Func<S, A, S> f) Source #
Fold the sequence while there are more items remaining
method ValueTask<S> FoldWhile <S> ( S state, Func<A, Func<S, S>> f, Func<(S State, A Value), bool> predicate) Source #
Fold the sequence while the predicate returns true and there are more items remaining
method ValueTask<S> FoldWhile <S> ( S state, Func<S, A, S> f, Func<(S State, A Value), bool> predicate) Source #
Fold the sequence while the predicate returns true and there are more items remaining
method ValueTask<S> FoldUntil <S> ( S state, Func<A, Func<S, S>> f, Func<(S State, A Value), bool> predicate) Source #
Fold the sequence until the predicate returns true or the sequence ends
method ValueTask<S> FoldUntil <S> ( S state, Func<S, A, S> f, Func<(S State, A Value), bool> predicate) Source #
Fold the sequence until the predicate returns true or the sequence ends
method IteratorAsync<A> Merge (IteratorAsync<A> other) Source #
Interleave two iterator sequences together
Whilst there are items in both sequences, each is yielded after the other. Once one sequence runs out of items, the remaining items of the other sequence is yielded alone.
method IteratorAsync<(A First , A Second)> Zip (IteratorAsync<A> other) Source #
Zips the items of two sequences together
The output sequence will be as long as the shortest input sequence.
method ValueTask DisposeAsync () Source #
Dispose
method IAsyncEnumerator<A> GetAsyncEnumerator (CancellationToken token) Source #
Get enumerator
Parameters
| returns | ||
Nil iterator case
The end of the sequence.
Fields
field IteratorAsync<A> Default = new Nil() Source #
Properties
Methods
method IteratorAsync<A> Clone () Source #
Clone the iterator so that we can consume it without having the head item referenced. This will stop any GC pressure.
method IteratorAsync<A> Split () Source #
When iterating a sequence, it is possible (before evaluation of the Tail) to Terminate the current
iterator and to take a new iterator that continues on from the current location. The reasons for doing
this are to break the linked-list chain so that there isn't a big linked-list of objects in memory that
can't be garbage collected.
Parameters
| returns | New iterator that starts from the current iterator position | |
method ValueTask DisposeAsync () Source #
Cons iterator case.
Contains a head value and a tail that represents the rest of the sequence.
Methods
method void Deconstruct (out ValueTask<A> head, out ValueTask<IteratorAsync<A>> tail) Source #
class IteratorAsync Source #
Methods
method IteratorAsync<A> from <A> (IAsyncEnumerable<A> enumerable) Source #
Create an iterator from an IAsyncEnumerable
Parameters
| type | A | |
| param | enumerable | |
| returns | ||
method IteratorAsync<A> singleton <A> (A head) Source #
Construct a singleton sequence
Parameters
| type | A | Bound value type |
| param | head | Head item |
| returns | IteratorAsync | |
method IteratorAsync<A> Cons <A> (A head, IteratorAsync<A> tail) Source #
Construct a sequence from a head item and a tail sequence
Parameters
| type | A | Bound value type |
| param | head | Head item |
| param | tail | Tail sequences |
| returns | IteratorAsync | |