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.