Joseph Kain bio photo

Joseph Kain

Professional Software Engineer learning Elixir.

Twitter LinkedIn Github

Notes

Infinite Streams

I was blown always by the first paragraph that defines an infinite stream in Scala. This is a very nice syntax for a recursively defined value. It wasn’t clear to me how to build this in Elixir:

val ones: Stream[Int] = Stream.cons(1, ones)

I first tried a recursive variable definition like this but it wouldn’t compile. I was writing a function to build the stream anyway so I simply wrote:

def build_infinite_stream_of_ones do
  L.cons(1, build_infinite_stream_of_ones)
end

Then I added a new test for map to try it out:

test "map is lazy on infinite stream" do
  assert L.map(build_infinite_stream_of_ones, &(&1 * 2)) |> L.take(5) |> L.to_list == [2, 2, 2, 2, 2]
end

I have to say I had that feeling of excitement and a little fear as I ran

$ mix test
...................

Finished in 0.1 seconds (0.1s on load, 0.00s on tests)
19 tests, 0 failures
$

and elation when I saw the result. It was nice, I haven’t been so pleasanlty suprised by a piece of code in a long time.

I wrote up the other examples from the text testing take_while and for_all. I was disappointed to see that my take_while did not terminate. However, take_while_via_fold worked so I’ll continue on.

Exercise 8, 9, 10

These are concrete examples that the text is using to show a pattern that it will generalize. These were no problem as I guess I’d already thought enough to adapt the stream of ones to Elixir.

def build_stream_of_constant(n), do: cons(n, build_stream_of_constant(n))

Exercise 11 - unfold

Fun, had to think a bit about what the function should look like before reading the text’s signature for unfold. A tuple would work and that’s what the text uses. I also checked Elixir’s Stream.unfold and it also uses a function that returns a tuple.

def unfold(acc, f) do
  {v, new_acc} = f.(acc)
  if v == nil do
    []
  else
    cons(v, unfold(new_acc, f))
  end
end

Here we call the function to get the value and the next accumulator then build the Cons struct. I wonder if there is a fancy way of writing this to make it a single expression.

Exercise 12 - using unfold

This is ano exercise to get some practice applying unfold. Pretty simple stuff.

Exercise 13 - using unfold some more

Pretty straightforward.

I realized I need to fix some problems with the termination - is this in the right section?

Exercise 14 - starts_with

I didn’t have too much trouble with this. Used zip to build a single stream of pairs and then foldr to compare them and accumulate true / false. Actually, I had a false start here that I didn’t realize until I wrote the third tests. Then I had trouble figuring out what went wrong. One big problem was that I didn’t make acc a function initially.

Exercise 15 - tails

Intersting that I can’t match streams

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

jkain-mbp:master ~/Documents/Projects/Personal/fp-elixir/laziness$ mix test lib/laziness.ex:28: warning: variable t is unused lib/laziness.ex:133: warning: variable f is unused lib/laziness.ex:164: warning: variable h is unused Compiled lib/laziness.ex Generated laziness.app ………………………………..

1) test tails (LazinessTest)
   test/laziness_test.exs:209
   Assertion with == failed
   code: 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, []), [])))
   lhs:  %Laziness.Cons{head: #Function<12.107630196/0 in Laziness.unfold/2>,
          tail: #Function<13.107630196/0 in Laziness.unfold/2>}
   rhs:  %Laziness.Cons{head: #Function<34.115577090/0 in LazinessTest.test tails/1>,
          tail: #Function<35.115577090/0 in LazinessTest.test tails/1>}
   stacktrace:
     test/laziness_test.exs:210

……

Finished in 0.3 seconds (0.3s on load, 0.00s on tests) 45 tests, 1 failures

The functions don’t match.

Try using flat_map and to_list as usual to resolve it.

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

Exercise 16 - this is hard

I started off trying to use unfold as I did in ex 15. But it wasn’t really clear what to do.