Streams
Source and Sink are the asynchronous siblings of Iterator and Accumulator, with the same verbs, the same Stage values in between, and a failure that stands in the type on both ends.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
A stream is one flow, in one direction, with two ends. Source<Item, Failure> reads with next, exactly like
Iterator; Sink<Item, Failure> writes with add and ends with end, like Accumulator - the only difference is
that every verb answers a Task. Failure is the stream's own failure and nothing else: a cancellation is not one,
because await() passes it on to the task that waits instead of answering it (Tasks, rule 9). So every
pull and every write is one .await()?.
Example#
fn countOf(var source: Source<Int, ChannelClosed>): Task<Result<Int, ChannelClosed>> {
source.collect(counting()).await()
}
const total = countOf Source<Int, ChannelClosed>.from([1, 2, 3])
print total.await().orElse(0)Syntax#
shared trait Source<Item, Failure> with Close {
var fn next(): Task<Result<Item?, Failure>>
}
shared trait Sink<Item, Failure> with Close {
var fn add(item: Item): Task<Result<Void, Failure>>
var fn end(): Task<Result<Void, Failure>>
}
while const Some(item) = source.next().await()? { ... }Rules#
Ok(Some(item))is the next item,Ok(None)is the end of the stream,Fail(problem)is a failure that ends it. AfterOk(None)every furthernext()answersOk(None)again; after aFailthe stream never delivers another item.Both ends are shared objects, so reading from a source or writing to a sink needs no
varbinding. A change of an object is not a question of the path it is reached through (Shared types, rule 2): every holder reads from the one stream, and an item one of them took is gone for all of them.varon a binding of a source only means that the binding may be pointed at another one.There is deliberately no
forover aSource. Aforhead has no place for the?that a failing pull needs, so the loop is awhilethat names bothawait()and?:fn printAll(var source: Source<Int, ChannelClosed>): Task<Result<Void, ChannelClosed>> { while const Some(item) = source.next().await()? { print item } Ok void }Backpressure is the shape of the protocol, not a mechanism. Reading pulls: nothing happens until
next()is called. Writing waits:add'sTaskfinishes only once the target has taken the item, so a writer faster than its target waits by itself.The same
StageanIterateuses drives aSourcetoo, throughthrough(stage),map,filter,takeand the rest. A stage is synchronous and never asks where its values come from, somapping,filteringand every other stage of Pipelines work unchanged on both worlds.close()releases what is above or below, synchronously and without failing;end()is the graceful end of aSinkand can fail. No program callsclose(): the last release of a stream runs it (see Destructors). A reader that stops early (take, afindthat found it, an abandoned loop) lets go of what it was reading from, and its release closes it; a sink that is released without being finished may have written less than it was given.
What this is not#
A Source is not something a for loop can read. Only Iterate works after in; a Source needs the while
form because its pull can fail.
fn printAll(var source: Source<Int, ChannelClosed>): Task<Result<Void, ChannelClosed>> {
while const Some(item) = source.next().await()? {
print item
}
Ok void
}fn printAll(source: Source<Int, ChannelClosed>) {
for item in source {
print item
}
}
// error: `Source<Int64, ChannelClosed>` is not `Iterate`, so `for` cannot walk itRelated#
- Channels -
Channel, the one place aSourceand aSinkare made from nothing else. - Tasks -
Taskandawait(), which every verb of a stream answers with. - Pipelines - the
Stagevalues a stream reuses without change. - Shared types - why a
var fnmethod here may answer aTask.