Exercise 3: add-squares
test "1.18-3: add-squares" do assert(add_squares([1, 2, 5]) == 30) end
My Elixir version takes a list rather than a variable number of arguments, because Erlang doesn’t do the varargs thing.
As for implementation…
def add_squares([n|ns]), do: n*n + add_squares(ns) def add_squares([]), do: 0
Elixir is all about recursion and pattern matching.
(Aside: I understand why it’s there, but that darn comma after the method and before the do: when writing one-liners gets me every freakin’ time.)
Hmmm… so far FPOO has avoided introducing defun, instead defining functions by creating them anonymously and then assigning them to a name. I’m suddenly curious how easy this is in Elixir.
add_squares = fn [n|ns] -> n*n + add_squares(ns) [] -> 0 end
I realized halfway through writing this that it will lead to a compile error:
(CompileError) lib/fp_oo_elx/exercises.ex:17: function add_squares/1 un defined
I remember now. There’s no way (that I’m aware of) to refer to an anonymous function within itself, so we can’t do recursive anonymous functions in Elixir.
(Aside: why is there no do after fn???)
OK, I guess I’ll stick to named functions. I can always take anonymous-style references to named functions with the capture (&) operator, so hopefully this won’t get in the way.
Exercise 4: Bizarro-factorial
test "1.18-4: bizarro-factorial" do assert(bizarro_factorial(5) == 120) end
def bizarro_factorial(n) do (1..n) |> Enum.reduce(&(&1 * &2)) end
This is probably a gratuitous use of the pipeline operator (|>), but I don’t care!
Note that FPOO specifies that I use only range and apply. The range operator (..) is the Elixir equivalent of range. On the other hand, apply doesn’t really translate well. Sure, Elixir has it. But Erlang (and thus Elixir) doesn’t have the concept of functions that take arbitrary numbers of arguments, the way * in lisp can yield the product of an arbitrary number of numbers:
(* 1 2 3 4 5)
Instead, Kernel.* is a strictly binary operator. So I have to cheat and use Enum.reduce, which is a recursive function under the covers.
Exercise 5: Various sequence functions
test "1.18-5: sequence functions" do # take assert(Enum.take([1,2,3], 2) == [1,2]) # distinct assert(Enum.uniq([1,2,1,3,2]) == [1,2,3]) # concat assert(Stream.concat([[1,2], [3,4]]) |> Enum.take(4) == [1,2,3,4]) # repeat xs = Stream.repeatedly(fn -> "x" end) assert(xs |> Enum.take(3) == ["x", "x", "x"]) # interleave # there appears to be no interleave. There's Enum.zip, which only # zips two collections, and isn't lazy(?). # drop assert((1..4) |> Enum.drop(2) == [3,4]) # drop-last assert((1..4) |> Enum.slice(0..-2) == [1,2,3]) # flatten assert(List.flatten([[1,2], [3,4]]) == [1,2,3,4]) # partition assert((1..10) |> Enum.partition(&Integer.even?(&1)) == {[2,4,6,8,10], [1,3,5,7,9]}) # every? assert([2,4,6] |> Enum.all?(&Integer.even?(&1)) == true) assert([1,4,6] |> Enum.all?(&Integer.even?(&1)) == false) # remove assert((1..10) |> Stream.reject(&Integer.even?/1) |> Enum.take(5) == [1,3,5,7,9]) end
These translations were delightfully easy to do; in almost every case, the Elixir version of the Clojure function either had a) the same name; or b) the name of the equivalent operation in Ruby (e.g. remove becomes reject).
One thing that has stood out as I’ve worked through these is that the Stream module is a lot more limited than the Enum module. And from my brief experimentation, Enum functions are not lazy. So, for instance, this expression will never return, even though we are only trying to take the first three unique items from the stream:
[1,2,3,4,5] |> Stream.cycle |> Enum.uniq |> Stream.take(3)
Evidently Enum.uniq tries to convert the stream into a fixed collection, rather than returning a filtered stream in this case.
Since FPOO is already talking about laziness a lot (and all sequences seem to be treated as lazy and potentially infinite in Clojure), this may become a problem for later examples. More broadly, this doesn’t bode well for writing truly generic functions that can process any kind of collection, including streams, in Elixir. However, Elixir is still very young, and I suspect that the Stream library will grow with time.
Exercise 6: prefix-of?
test "1.18-6: prefix-of?" do assert(prefix_of?([1,2], [1,2,3,4]) == true) assert(prefix_of?([2,3], [1,2,3,4]) == false) end
def prefix_of?(candidate, sequence) do import Enum size = count(candidate) subseq = sequence |> take(size) candidate == subseq end
Exercise 7: tails
test "1.18-7: tails" do assert([1,2,3,4] |> tails == [[1,2,3,4], [2,3,4], [3,4], [4], []]) end
def tails([_|xs] = sequence), do: [sequence|tails(xs)] def tails([]), do: [[]]
Marick says “my solution is very much in the functional style”, and then goes on to offer some hints having to do with using Clojure’s range and map. I’m going to go out on a limb and say that the solution I came up with first is even more in the functional style, since it relies entirely on destructuring and recursion and doesn’t require any library calls at all.
Just for fun, here’s a more direct translation of Marick’s solution:
def tails2(seq) do import Enum 0..count(seq) |> map(&drop(seq, &1)) end
His solution is actually a bit more involved than this, because it involves mapping over both the range 0..count(seq) and a repeated list of the sequence itself. I’m guessing this is because he hasn’t yet explicitly introduced lambdas apart from the top-level function definitions.
(def tails (fn [seq] (map drop (range (inc (count seq))) (repeat (inc (count seq)) seq))))
If nothing else, this demonstrates that Clojure’s map can map over multiple sequences in parallel, which is kinda cool.
Recursive anonymous functions in the Erlang VM, I believe, are implemented but locked away in EEP37. I suspect once that patch is accepted, Elixir should get them too (source).
Neat!
Why not in Erlang?
Mostly ’cause I don’t know Erlang!
Neat idea. Inspired me to do the same thing in Scala. Only Chapter 1 so far: http://bradcollins.com/2013/12/21/fpoo-chapter-1-in-scala/