Question

Question: is it possible to create an Elixir Stream that keeps some kind of state internally that changes with each call? How would code look that does it?

I was delighted to see Elixir 0.10.2 having Streams in addition to the normal Enum module. While there is documentation on how to use Streams there's little to no documentation nor globally searchable documents that describe how to create them. Reading the source code for the Stream module is not very enlightening. I've also studied Lazily concatenate an enumerable of lists and its answer, and while very interesting it doesn't touch on changing state either.

Think of generating Fibonacci numbers where you need to remember the previous two values. Or think of Clojure's lazy sequence for prime numbers.

My problems with understanding whether or not this is possible in the first place are:

  1. The record's enumerable element is only set by some examples, and always to an existing, fixed list. The examples that deal with infinite sequences don't touch enumerable at all (e.g. iterate or repeat).
  2. The function provided with fun is supposed to return another function, not a new Stream.Lazy record which, as far as I can tell, is the big difference to Clojure's lazy sequences where you return a new sequence with lazy-seq.
  3. How the accumulator acc is used is still somewhat beyond me. It looks like the fun doesn't modify it itself; instead some outer function calling the fun does.

Hence my question.

Was it helpful?

Solution

Stream.unfold/2 lets you generate a sequence of values while maintaining an "accumulator" (state).

I just built a lazy Fibonacci sequence function using it here: https://github.com/seven1m/30-days-of-elixir/blob/master/24-stream.exs#L31-33

OTHER TIPS

I just played around the stream. I might be missing the point, but for maintaining the state, exactor (gen_server) could be used?

https://github.com/sasa1977/exactor

Definition

defmodule Sample do
  use ExActor

  defcall next(x), state: state, do: reply(state + x, x)

  def fib(n) do
    {:ok, act} = Sample.start(1)
    Stream.iterate(0, fn(x) -> Sample.next(act, x) end) |> Enum.take(n)
  end
end

Run

iex(3)> Sample.fib(5) 
[0, 1, 1, 2, 3]
iex(4)> Sample.fib(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

I'm no expert in lazy streams, but here's my attempt: https://gist.github.com/sasa1977/6699577

Note however, that in this example, state is public, rather than internal, so I don't know if this is what you're looking for.

The solution relies first on creating a record (in this case private), and exposing a function which can move the record to the next state:

fib0 = Fibonacci.new
fib1 = Fibonacci.next(fib0)
fib2 = Fibonacci.next(fib1)
fib3 = Fibonacci.next(fib2)
IO.inspect fib3

Each fibx contains the full state. In this case, I'm keeping the current number, plus the previously two generated numbers.

Based on this, I can now use Stream.iterate to make an endless lazy collection:

Fibonacci.lazy
|> Enum.take(5)
|> IO.inspect

Since it is based on Fibonacci.next, each generated element is a record, rather than a value. You can use Fibonacci.value to extract value from each element.

A more complex manipulations can also be performed:

Fibonacci.lazy
|> Stream.drop(3)                                           # drop first three
|> Enum.first                                               # take the first
|> Fibonacci.lazy                                           # start lazy from it
|> Stream.filter(&(rem(Fibonacci.value(&1), 2) == 1))       # take only odd values
|> Stream.map(&(Fibonacci.value(&1)))                       # extract values from it
|> Enum.take(10)                                            # take first 10
|> IO.inspect                                               # print it
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top