Saturday, January 25, 2014

Looking at Elixir

The Pragmatic Programmers had a big impact on me with the PickAxe book introducing Ruby, among many other things.

So when Dave Thomas gets excited about a new language, I get excited too. His new book "Programming Elixir", is in a quite different style from the PickAxe, much briefer, just tries to give an overview and a taste. This too is quite appealing. In this day of Google and Stack Overflow, we don't need hardcopy reference materials. (On the other hand, when I look at how little work I can get done these days without consulting Google, I have to admit those printed references must have actually been quite useful!)

Anyway, I've been working through this new book and Elixir a little, while on vacation. I'm no expert on Elixir, Erlang, Haskell, functional programming, or pretty much anything. Nevertheless I thought I might put a few of my impressions. Besides, this blog hasn't had enough activity, and with New Year's Resolutions and everything...

Here are the things that struck me.

Aesthetic.

It looks and feels a bit like Haskell, but ugly.

Perhaps not a fair comparison, because I feel that Haskell is the most beautiful programming language I have seen.

Length of a list, in Haskell:

len [] = 0
len (h:t) = 1 + len t

In Elixir:

def len([]), do: 0
def len([_|t]), do: 1 + length(t)

Extra 'def' keyword required, and what's with that ", do:=" gunk? Isn't Haskell's definition as clear and minimal as conceivably possible?

Another example: 'map'. In Haskell:

map _ [] = []
map f (h:t) = f h : map f t

In Elixir:

def map(_, []), do: []
def map(f, [h|t]), do: [f.(h) | map(f, t)]

It's the same, just with extra punctuation.

Laziness.

It seems like Elixir supports laziness only when explicitly using the Stream module. Functions cannot be defined recursively using list constructors to return infinite lists.

In Haskell:

repeat x = x : repeat x

In Elixir, maybe:

def repeat(v), do: Stream.iterate(v, fn _ -> v end)

It's an awkward definition because of the need to provide an inline function returning the constant value. But worse, the return type is not the same as a list.

For example, in Haskell we can define take:

take n (h:t) = if n==0 then [] else h : take (n-1) t

And then use it with 'repeat':

take 3 $ repeat 10

But with Elixir, the list definition of 'take':

def take(0, _), do: []
def take(n, [h|t]), do: [h|take(n-1, t)]

Does not work with streams:

iex(14)> Haskell.take(3, Haskell.repeat(1))
** (FunctionClauseError) no function clause matching in Haskell.take/2
  haskelllib.exs:55: Haskell.take(3, #Function<3 .80570171="" in="" stream.iterate="">)
  erl_eval.erl:569: :erl_eval.do_apply/6
  src/elixir.erl:138: :elixir.eval_forms/3

Although the builtin 'Enum.take' does work:

Enum.take(Haskell.repeat(1), 3)

But this returns a list, not a stream.

OK, for 'take' it may often be fine to create a full list rather than a lazy list. But how about 'map'?

The list version of 'map' can be defined:

def map(_, []), do: []
def map(f, [h|t]), do: [f.(h) | map(f, t)]

But this version cannot be used with streams, and we really do want lazy 'map' with lazy streams.

To be fair, this is probably a conscious decision in Elixir to separate lists and streams, just as it is in Scala. I guess that there is a fair tradeoff to be made about laziness, e.g. see this blog.

Function parameter ordering.

You might have noticed that in the above definitions of 'take', the standard Elixir version defines the arguments in a different order from the Haskell version.

The reason is that in Elixir, for the pipe operator '|>' to work, a function must take a list as its first parameter.

[1,2,3,4,5] |> Enum.take(2)

But it seems to me that it's better to give lists as the last argument, as in Haskell, for the purpose of currying:

f = filter even
f [1,2,3,4,5,6,7]

It is perhaps telling that in "Programming Elixir", the word "curry" does not occur. (Nor does "partial" in the sense of partially applied function.)

Having said that, I have to admit that the pipe operator looks appealing, largely due to its nice left-to-right reading. The example in the Prag book is:

filing = DB.find_customers
|> Orders.for_customers
|> sales_tax(2013)
|> prepare_filing

I suppose that in Haskell this would look something like this:

filing = prepare_filing $ sales_tax 2013 $ Orders_for_customers $ DB_find_customers

The right-to-left reading of Haskell is one of the least-appealing aspects of it for me, sometimes.

Summary

For me, for learning Functional Programming, I think I'll stick with Haskell. It seems like a nicer language and there are some good resources.

On the other hand, if you are on the Erlang platform, or want to be, maybe because of the nice concurrency or high availability features, then you might find Elixir an attractive alternative to (or complement to) the Erlang language.