Joseph Kain bio photo

Joseph Kain

Professional Software Engineer learning Elixir.

Twitter LinkedIn Github

Last week I fell behind in my blogging and only posted my raw notes for the exercises in “Infinite Streams and Corecursion” in Functional Programming in Scala. Elixir Fountain was kind enough to announce last week’s post so I’ve opted to leave it as is. But this week I’ll written up this small post to fill in some of the missing details in the raw notes. I will discuss Elixir’s standard Streams module next week.

I continued using the same github repo from the first part of this exploration. If you want to work through the exercises for yourself, then follow the directions in the repo’s README before reading the rest of this post.

Exercise 15 - tails

This exercise asks us to implement a function called tails which returns all tails of the stream starting with the original list. It should be implemented using unfold

My notes from last week start to fall apart at exercise 15, at least in terms of describing the tails function. What they do show are problems I encountered while trying to write up the test and solution.

The first thing that I point out in the notes is that the cons notation is less convinient than the built-in list and stream notation [h | t]. For example, I can’t write the following as a test:

test "tails" do
  assert L.tails(build_stream) == L.cons(
    L.cons(1, L.cons(2, L.cons(3, []))),
    L.cons(
      L.cons(2, L.cons(3, [])),
      L.cons(
        L.cons(3, []),
        []
      )
    )
  )
end

because Elixir / Erlang matching can’t match through these cons calls.

I instead ended up using flat_map to flatten the result of tails and then using to_list as I have in all the other tests. While this does give a nice small test it completely removes the structure that tails is expected to produce form the test.

test "tails" do
  assert L.tails(build_stream) |> L.flat_map(&(&1)) |> L.to_list == [1, 2, 3,  2, 3,  3]
end

I’ve been thinking that Elixir allows me to write my own implementation for its [h | t] operator. It would be interesting to try doing this for my streams implementation. I wonder if it would improve the matching that is possible in these tests. I will also explore this when I look into the Elixir standard streams library next week.

Exercise 16 - scan

As I mentioned last week, this was a rather difficult exercise for me. The exercise is to implement a function scanRight which is a generalization of tails. It should be similar to foldRight except that it returns a stream of the intermediate results. The text goes on to require that scanRight reuse the intermediate results as it constructs each successive element in the result.

I chose to name my function scan after Elixir’s standard Stream.scan. Then I wrote the test. Then I thought about the tests question: ““Can it be implemented using unfold?” I figured that scan could be implemented using unfold but not efficiently because unfold iterates through the stream in the wrong order making it impossible to reuse the intermediate results.

Even though a implementation based on unfold would not be efficient I wrote it anyway. This was a good exercise for my test and TDD suggests that we write up a simple implementation first. The function calls foldr each time and isn’t able to reuse the result.

def scan(s, acc, f), do: unfold(s, fn
  (:terminate) -> nil
  ([]) -> {0, :terminate}
  (%Cons{head: h, tail: t}) -> { foldr(cons(h.(), t.()), ld(acc), fn (x, acc) -> f.(x, acc.()) end), t.() }
end)

I tried anoter version that I thought were more efficient. After writing them I realized I didn’t have a good way of proving they were efficient so I wrote a test to enforce the efficiency requirement:

test "scan should reuse intermediate results" do
  {:ok, counter} = Agent.start_link(fn -> 0 end)
  count_calls = fn
    (_, _) -> Agent.get_and_update(counter, fn (current) -> {current + 1, current + 1} end)

  end

  assert L.scan(build_stream, 0, count_calls) |> L.to_list == [3, 2, 1, 0]
end

This test starts by creating an Agent to hold a counter. Then I create a function to call the Agent to increment and the counter. The assertion calls scan with the increment function. This way, for a 3 element stream I can confirm that the function passed to scan was called only 3 times during the construction of the result.

Even with this test I still had trouble coming up with the right solution. But I was at able least recognize when I had an incorrect solution. Ultimately, I had to look at the text’s solution which used foldr. I had tried thinking through foldr based solutions but missed the trick that the text employed. The key is is maintain more state in the foldr than just the resulting stream. Then when the foldr completes extract the stream and return it. After learning this lesson I wrote the folling, efficient, solution and passed my tests:

def scan(s, initial, f) do
  {_total, result} = foldr(s, ld({initial, cons(initial, [])}),
    fn (x, lazy) ->
      {val, acc} = lazy.()
      head = f.(x, val)
      {head, cons(head, acc)}
    end
  )
  result
end

This scan function uses foldr and at ever iteration it has a tuple containing val which is the last value computed and acc which is the accumulated stream. val is used when calling f so as to efficiently reuse intermediate computation. acc is used to build up the resulting stream. The tuple returned by foldr is destructured into _tatal (the final val) and result the final stream. result is returned from scan.