stream
A library of channel-backed sources, transforms, and sinks. Streams are
simply Channel values used as data flows — the
underlying primitive is unchanged. Each source and each transform is a
stage: a task that reads its input channel, calls the user closure, and
writes to the output channel. A stage waits like any task, so a pipeline
of many stages holds no thread while it waits. Backpressure is provided by
channel capacity (default 16; configurable via stream.buffered).
Sinks (collect, fold, count, etc.) drain a channel in the calling
task, which waits for each value.
See also io / fs for the underlying file operations behind
file_chunks / file_lines, tcp for tcp_chunks / tcp_lines,
and channel / task for the primitive channel operations.
Summary
Sources
| Function | Signature | Description |
|---|---|---|
from_list | (List(a)) -> Channel(a) | Emit list elements then close |
from_range | (Int, Int) -> Channel(Int) | Emit lo..=hi then close |
repeat | (a) -> Channel(a) | Infinite — pair with take |
unfold | (a, Fn(a) -> Option((b, a))) -> Channel(b) | Generator (closes on None) |
file_chunks | (String, Int) -> Channel(Result(Bytes, IoError)) | Read file in chunks |
file_lines | (String) -> Channel(Result(String, IoError)) | Read file line-by-line |
tcp_chunks | (TcpStream, Int) -> Channel(Result(Bytes, TcpError)) | Read TCP in chunks |
tcp_lines | (TcpStream) -> Channel(Result(String, TcpError)) | Read TCP line-by-line |
Transforms
| Function | Signature |
|---|---|
map | (Channel(a), Fn(a) -> b) -> Channel(b) |
map_ok | (Channel(Result(a, b)), Fn(a) -> c) -> Channel(Result(c, b)) |
filter | (Channel(a), Fn(a) -> Bool) -> Channel(a) |
filter_ok | (Channel(Result(a, b)), Fn(a) -> Bool) -> Channel(Result(a, b)) |
flat_map | (Channel(a), Fn(a) -> List(b)) -> Channel(b) |
take | (Channel(a), Int) -> Channel(a) |
drop | (Channel(a), Int) -> Channel(a) |
take_while | (Channel(a), Fn(a) -> Bool) -> Channel(a) |
drop_while | (Channel(a), Fn(a) -> Bool) -> Channel(a) |
chunks | (Channel(a), Int) -> Channel(List(a)) |
scan | (Channel(a), b, Fn(b, a) -> b) -> Channel(b) |
dedup | (Channel(a)) -> Channel(a) where a: Equal |
buffered | (Channel(a), Int) -> Channel(a) |
Combinators
| Function | Signature |
|---|---|
merge | (List(Channel(a))) -> Channel(a) |
concat | (List(Channel(a))) -> Channel(a) |
zip | (Channel(a), Channel(b)) -> Channel((a, b)) |
Sinks
| Function | Signature |
|---|---|
collect | (Channel(a)) -> List(a) |
fold | (Channel(a), b, Fn(b, a) -> b) -> b |
count | (Channel(a)) -> Int |
first | (Channel(a)) -> Option(a) |
last | (Channel(a)) -> Option(a) |
write_to_file | (Channel(Bytes), String) -> Result((), IoError) |
write_to_tcp | (Channel(Bytes), TcpStream) -> Result((), TcpError) |
Examples
Three-step pipeline
import stream
fn main() {
let squares = stream.from_range(1, 100)
|> stream.filter { n -> n % 2 == 1 }
|> stream.map { n -> n * n }
|> stream.take(5)
|> stream.collect
println(squares)
}open in playgroundGenerator via unfold
import stream
fn main() {
-- Generate 1, 2, 3, 4, 5 then None.
let xs = stream.collect(
stream.unfold(1) { n ->
match n > 5 {
true -> None
false -> Some((n, n + 1))
}
},
)
println(xs)
}open in playgroundDesign notes
- Streams are channels. No new value type.
stream.collect(ch)works on anyChannel, not just streams produced by this module. - Backpressure is automatic. When the output channel of a stage is full, the stage waits for room, and so consumes no further input.
- A failing stage fails the pipeline. If the function of a stage
raises a runtime error (a division by zero, a
panic), the stage closes its output with that failure. The next stage fails with it in turn, and the sink at the end raises it:stream.collectdoes not return the values that came before the failure.channel.eachon a stream raises it too; a plainchannel.receiveseesClosed. - A pipeline that is cut short stops. When a stage or a sink reads no
further (
take,take_while,first, a failure downstream), the stage that feeds it is stopped, and so on up to the source. Sostream.repeat(x) |> stream.take(3)leaves nothing running. The output of a stage is closed by this: read it with one consumer. A channel that no stage feeds (one you made withchannel.new) is left open. What is left in it is another matter: a stage reads ahead of what its reader asks for (amapstage until its output is full, afilterstage as far as it finds nothing to pass on), and what a stage took is gone when the pipeline is cut short.stream.takedirectly on a channel takes exactly what it gives. - A TCP source or sink that is stopped closes its connection. When
stream.tcp_linesorstream.tcp_chunksis stopped before the peer has closed (stream.tcp_lines(conn) |> stream.first), or the task instream.write_to_tcpis cancelled, the connection is shut down, as bytcp.close: a read that may be in flight has to end, and what the source had read ahead is gone anyway. That holds at whatever moment the source is stopped once it has begun to read: in a read, or with an item that nobody takes any more. (A source that is stopped before it has read anything leaves the connection as it is.) Read withtcp.readif the connection is to be used afterwards. - A stream that you stop reading goes on. A stream that is read by
hand (
channel.receive,channel.select) and then left is still at work: its stages run until their outputs are full or the source is at its end, and the program does not end before they have stopped (see When a program ends).channel.close(s)on the stream stops them at once. - Errors flow through the stream. File sources emit
Channel(Result(_, IoError)); TCP sources emitChannel(Result(_, TcpError)). Each chunk can fail independently; consumers pattern-match. Usemap_ok/filter_okto apply transformations only toOkvalues, passingErr(_)through unchanged. - No async/await. Stages are tasks of the silt scheduler; file and socket reads run on the I/O pool while the stage waits.
stream.repeatis infinite. Always pair it withtake,take_while, or another bounded sink —collecton an unbounded stream will hang.
Forward compatibility
Function names mirror what method-form dispatch (s.map(f)) would look
like once silt grows a Stream trait. Existing silt programs will
continue to compile and behave identically when that trait lands.