Streams

A number of the data structures in PureFun.jl rely on lazy evaluation and lazily evaluated lists, which are provided by PureFun.Lazy. Here we look at some toy examples to get a feel for to use Streams.

using PureFun
using PureFun.Lazy: Stream, @stream

Basics

"Lazy evaluation" describes a strategy for evaluating expressions, and has two main features:

  • The evaluation of the expression is delayed (suspended) until its result is needed

  • The result is cached (memoized) the first time the expression is evaluated, so that subsequent evaluations become cheap lookups

Streams are lazily evaluated lists, and are described in section 4.2 of the book:

Streams (also known as lazy lists) are similar to ordinary lists, except that every cell is systematically suspended

integers = Stream(Iterators.countfrom(1))
PureFun.Lazy.NonEmpty{Int64}
1
2
3
4
5
6
7
...

The @stream macro enables you to build streams by providing an expression which evaluates to the first element, and one that evaluates to a stream of remaining elements. Both expressions are suspended, and only evaluated when the value is needed (at which time, the value is cached).

fibonacci(a,b) = @stream(Int, a, fibonacci(b, a+b))
fibonacci(0, 1)
PureFun.Lazy.NonEmpty{Int64}
0
1
1
2
3
5
8
...

Comparison to iterators

Like Streams, Julia's iterators are also lazily evaluated. The main difference is that Streams are memoized, meaning that values that have been calculated are cached and can be revisited without having to recalculate them.

Like all of the data structures in PureFun.jl, Streams are iterators themselves, and calling a function from Base.Iterators on a Stream works as expected. Calling Stream on an iterator, on the other hand, is kind of like a lazy collect, it materializes computed values as they are iterated out:

using Base.Iterators: zip, drop, take

foo = map(x -> 2x, accumulate(+, filter(isodd, integers), init=0.0)) |> Stream
bar = zip(foo, drop(foo, 1)) |> Stream

collect(take(bar, 7))
7-element Vector{Tuple{Float64, Float64}}:
 (2.0, 8.0)
 (8.0, 18.0)
 (18.0, 32.0)
 (32.0, 50.0)
 (50.0, 72.0)
 (72.0, 98.0)
 (98.0, 128.0)

We can access elements of streams the same way we would do for regular (eager) lists:

bar[2000]
(8.0e6, 8.008002e6)

Reference

PureFun.Lazy.StreamType
Stream{T} <: PureFun.PFStream{T}

Stream with elements of type T. Every cell in a stream is systematically suspended, and only evaluated when the value in that cell is required. Furthermore, the value is cached the first time a cell is evaluated, so that subsequent accesses are cheap. Introduced in $\S{4.2}$

source

This page was generated using Literate.jl.