kex docs Standard Library 0.4.0-alpha kex.run ↗

Streamprelude

type Stream<A>

A lazy, potentially infinite sequence.

A stream describes how to produce its elements rather than holding them, so an infinite one is an ordinary value. Nothing is computed until you ask for elements with take.

let naturals = Stream.Sequence(from: 0) { |n| n ` 1 }
naturals.take(5)                              # => [0, 1, 2, 3, 4]
naturals.map { |n| n * n }.take(4)            # => [0, 1, 4, 9]
naturals.filter { |n| n.even? }.take(3)       # => [0, 2, 4]

map, filter and drop all answer with another stream, so a pipeline stays lazy end to end; take is what turns it into a list.

Streams are best for generated sequences you may revisit. A file or socket is different: it can only be consumed once, so those APIs return a `Feed`. Convert a small feed with `toStream+ only when replaying it is worth keeping every value already read.

module Stream

Constructors for Stream.

function Sequence

Builds an infinite stream from a first element and a step function.

The stream is from, then step(from), then step(step(from)), and so on: nothing is computed until you take from it.

Sequence(from, step)
Parameters
from A
the first element
step A -> A
produces the next element from the current one

Returns: Stream<A> — the generated stream

Examples

Counting up

let naturals = Stream.Sequence(from: 0) { |n| n + 1 }
naturals.take(5)   # => [0, 1, 2, 3, 4]

Powers of two

let powers = Stream.Sequence(from: 1) { |n| n * 2 }
powers.take(6)     # => [1, 2, 4, 8, 16, 32]

A geometric decay

Stream.Sequence(from: 1.0) { |x| x / 2.0 }.take(4)
# => [1.0, 0.5, 0.25, 0.125]

function Iterate

Builds an infinite stream from a seed and a step function. The same thing as Sequence: use whichever reads better where you are.

Iterate(seed, step)
Parameters
seed A
the first element
step A -> A
produces the next element from the current one

Returns: Stream<A> — the generated stream

Examples
let odds = Stream.Iterate(1) { |n| n + 2 }
odds.take(5)   # => [1, 3, 5, 7, 9]

constant empty ?

The stream with no elements.

make Stream<A>

take

Returns the first n elements as a list, computing the stream up to that point.

This is the operation that ends a lazy pipeline and gives you real data.

take(n) : Integer -> [A]
Parameters
n Integer
how many elements to produce

Returns: [A] — the first n elements

Examples
Stream.Sequence(from: 1) { |n| n ` 1 }.take(3)   # => [1, 2, 3]

The first ten squares

Stream.Sequence(from: 1) { |n| n ` 1 }
  .map { |n| n * n }
  .take(10)

Generating retry delays without building an unbounded list

let delays = Stream.Sequence(from: 1.seconds) { |d| d * 2 }
delays.take(4)   # => [1 second, 2 seconds, 4 seconds, 8 seconds]

drop

Returns a new stream that skips the first n elements.

Still a stream, so the result stays lazy: pair it with take to get a window out of the middle.

drop(n) : Integer -> Stream<A>
Parameters
n Integer
how many elements to skip

Returns: Stream<A> — the stream, offset by n

Examples
Stream.Sequence(from: 0) { |n| n + 1 }.drop(3).take(3)   # => [3, 4, 5]

Paging through a generated sequence

let page(n: Integer) = source.drop(n * 20).take(20)

map

Returns a new stream with f applied to each element.

f is not called until elements are taken, and then only for those that are.

map(f) : (A -> B) -> Stream<B>
Parameters
f A -> B
applied to each element

Returns: Stream<B> — the mapped stream

Examples
Stream.Sequence(from: 1) { |n| n ` 1 }.map { |n| n * n }.take(4)
# => [1, 4, 9, 16]

Formatting as it goes

Stream.Sequence(from: 1) { |n| n ` 1 }
  .map { |n| "item ${n}" }
  .take(3)
# => ["item 1", "item 2", "item 3"]

filter

Returns a new stream with only the elements pred accepts.

Producing n filtered elements may require walking many more upstream ones, so a predicate that almost never holds makes take run for a long time, and one that never holds makes it run forever.

filter(pred) : (A -> Bool) -> Stream<A>
Parameters
pred A -> Bool
the test applied to each element

Returns: Stream<A> — the filtered stream

Examples
let evens = Stream.Sequence(from: 0) { |n| n ` 1 }.filter { |n| n.even? }
evens.take(4)   # => [0, 2, 4, 6]

Multiples of three, formatted

Stream.Sequence(from: 1) { |n| n ` 1 }
  .filter { |n| n.modulo(3) == 0 }
  .map { |n| "#${n}" }
  .take(3)
# => ["#3", "#6", "#9"]

each

Applies f to every element.

Only ever finishes on a stream that ends: a file's lines converted with Feed.toStream, or anything take has bounded. On Stream.Sequence this runs forever, exactly as writing the same loop by hand would.

each(f) : (A -> Void) -> Void
Parameters
f A -> Void
applied to each element

Returns: Void

Examples
Stream.Sequence(from: 1) { |n| n + 1 }
  .map { |n| n * n }
  .toFeed
  .take(3)
  .each { |n| IO.printLine(n) }