kex docs Standard Library

Feed

type Feed<A>

A one-shot sequence over a source that can only be read once.

A feed is what Stream is not: reading it CONSUMES it. Where a stream remembers its elements so it can be walked again, a feed keeps nothing, and that is the point: it is how to walk a file, a socket or a device larger than memory, which a stream cannot do while anything still holds its start.

foul lines = FS.File.feed("huge.log").or(Feed.empty)
lines.each { |line| IO.printLine(line) if line.contains?("ERROR") }

map, filter and drop answer a feed over the SAME cursor rather than a second cursor over the same source, so a pipeline is one pass:

FS.File.feed("app.log").or(Feed.empty)
  .filter { |line| line.contains?("ERROR") }
  .take(10)

Taking twice answers two different windows: the first ten, then the ten after them. That is the whole difference from Stream, where taking twice answers the same ten. When you want the stream behaviour on a small source, toStream asks for it explicitly, at the cost of holding what it reads.

Functions

Reading a feed consumes its cursor. Obtain a new feed when you need to traverse the same data again. Opening a file-backed feed is foul; consuming an existing feed does not require another capability.

pull

pull : A?

Answers the next element and consumes it, or None once the source is spent.

NOT named next: that is the loop-continue keyword, and the lexer reads it as one even after a dot, so a call to it silently swallows the rest of the enclosing block.

Returns the next element, or None

Examples
let feed = Feed.Elements([1, 2])
feed.pull   # => Just(1)
feed.pull   # => Just(2)
feed.pull   # => None

spent?

spent? : Bool

Whether the source has run out.

false does not promise another element: only that the feed has not been told otherwise yet. The source is asked, and may end, on the next read.

Returns true once the feed is spent

Examples

Draining a feed a piece at a time

let jobs = Feed.Elements(["resize", "index"])
jobs.spent?   # => false
jobs.take(2)
jobs.spent?   # => true

take

take(n: Integer) -> [A]

Returns the next n elements as a list, consuming them.

Answers fewer than n when the source ends first, and [] once it is spent. Unlike Stream.take, calling it twice walks forward.

Parameters
n
how many elements to consume

Returns up to n elements

Examples

Two windows, not the same one twice

let feed = Feed.Elements([1, 2, 3, 4])
feed.take(2)   # => [1, 2]
feed.take(2)   # => [3, 4]

drop

drop(n: Integer) -> Feed<A>

Discards the next n elements and answers the feed.

There is only ever one cursor, so this hands back the same feed rather than a second one positioned differently.

Parameters
n
how many elements to discard

Returns the same feed, advanced

Examples
FS.File.feed("log.txt").or(Feed.empty).drop(1).take(3)

map

map(f: (A -> B)) -> Feed<B>

Returns a feed with f applied to each element.

f runs as elements are read, and only for those that are. The result draws from the same cursor, so reading it consumes the receiver too.

Parameters
f
applied to each element

Returns the mapped feed

Examples
FS.File.feed("log.txt").or(Feed.empty).map(~trim).take(5)

filter

filter(pred: (A -> Bool)) -> Feed<A>

Returns a feed of only the elements pred accepts.

Reading one element of the result may consume many of the receiver's.

Parameters
pred
the test applied to each element

Returns the filtered feed

Examples

The first ten errors in a long log

FS.File.feed("app.log").or(Feed.empty)
  .filter { |line| line.contains?("ERROR") }
  .take(10)

each

each(f: (A -> Void)) -> Void

Applies f to every remaining element, draining the feed.

Parameters
f
applied to each element
Examples
FS.File.feed("log.txt").or(Feed.empty).each { |line| IO.printLine(line) }

collect

collect : [A]

Drains the feed into a list.

This is the operation that gives up constant space, which is why it is named rather than implicit: on a source too large to hold, use each.

Returns every remaining element

Examples
FS.File.feed("short.txt").or(Feed.empty).collect

Loading a small configuration file for repeated passes

let rules = FS.File.feed("rules.txt")
  .or(Feed.empty)
  .map(~trim)
  .filter(~present?)
  .collect

toStream

toStream : Stream<A>

Returns a Stream drawn from the feed.

The stream remembers what it reads, so it can be walked again: at the cost of holding every element forced through it. Worth it for a source small enough to replay; the opposite of what a feed is for otherwise.

Returns a replayable view of the remaining elements

Examples
let lines = FS.File.feed("small.txt").or(Feed.empty).toStream
lines.take(2)   # => ["one", "two"]
lines.take(2)   # => ["one", "two"] : a stream, so the same two

module Feed

Constructors for Feed.

empty constant

empty : Feed<A>

A feed with nothing in it, already spent.

Mostly the or half of a failed open: FS.File.feed(path).or(Feed.empty) reads nothing rather than stopping the program.

Examples
Feed.empty.take(3)   # => []

Elements

Elements(xs: [A]) -> Feed<A>

A feed over a list that is already in hand.

The elements are there, so nothing is saved by feeding them: this is for standing in for a real source, in a test, with something that consumes the way the real one does.

Parameters
xs
the elements to feed

Returns a feed over xs

Examples
let feed = Feed.Elements(["one", "two"])
feed.take(1)   # => ["one"]
feed.take(1)   # => ["two"] : the first one is gone