Elixir Syntax Help

Hello, I just started learning elixir and I am going through the book “Programming Elixir >= 1.6”.
I do not fully understand the syntax and this might be because I failed to understand the concept of pattern matching in function altogether.

In the book, here is an example of using the Stream module

What works:

iex> Stream.unfold({0,1}, fn {f1,f2} -> {f1, {f2, f1+f2}} end) |> Enum.take(15)

Return Value:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

I understand how the unfold function works, just having a hard time understand the “syntax” or the fucntion and the first argument.
Note: I put syntax in quotes because I am not sure if it’s the syntax I am not understanding, or is it how to define function definition with pattern matching.

In the code above:
accumulator (first argument): type is a tuple, and has two elements
function argument: a tuple of two elements. Returns a tuple of two elements where the first element is the new state and the second element is the value to calculate next state.

So why doesn’t this work?

iex(31)> Stream.unfold([1,2], fn([f1,f2]) -> [f1, [f2, f1+f2]] end) |> Enum.take(15)

Error:

** (CaseClauseError) no case clause matching: [1, [2, 3]]
    (elixir 1.13.0) lib/stream.ex:1648: Stream.do_unfold/4
    (elixir 1.13.0) lib/enum.ex:3295: Enum.take/2

How can using List throw this error when the following works?

iex(33)> f = fn [a,b] -> [b,a] end
iex(34)> f.([2,[4, 3]])
[[4, 3], 2]

Any help in understanding this issue would be much appreciated.
Thank you!

Some of the tuples in your original expression:

Stream.unfold({0,1}, fn {f1,f2} -> {f1, {f2, f1+f2}} end)

are perfectly happy being converted to lists (notated with [ and ]) because unfold doesn’t care about their specific shape. The outer tuple of the return value is different, because it’s specifically mentioned in unfolds typespec:

@spec unfold(acc(), (acc() -> {element(), acc()} | nil)) :: Enumerable.t()

Earlier in the Stream module, both acc and element are defined as any; the names are for human readers, the function itself doesn’t care about the shape of those values.

But the return type of the callback passed in the second argument - {element(), acc()} | nil specifically specifies the function should return either nil or a tuple. Returning a list instead (as in your second example) causes this case statement to fail to match:

3 Likes

Oh i see!
Thank you very much for the explanation! :smiley:

I completely glazed over the fact that return value is enclosed in curly brackets " { } ".
I translated that to any value (type) that implements the Collectable protocol.
Next time, I will read the code, as it is so easily accessible.

Thank you.

1 Like

Just to illustrate @al2o3cr answer with a list accumluator:

Stream.unfold([0,1], fn([f1,f2]) -> {f1, [f2, f1+f2]} end) |> Enum.take(15)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
1 Like