Space Vatican

Ramblings of a curious coder

Stuff That in Your Pipe(line) and Smoke It

I’ve just started learning elixir (reading through the excellent Programming Elixir at the moment) and although it’s still very early days I’m already in love. Pattern matching for example is something I remembered fondly from my days dabbling with ML. As Dave says in the book though, there’s a bit of a leap of faith involved in leaving behind the object oriented world. I’m used to being able to do things like

1
headers.keys.map {|key| key.to_s.downcase}.sort.join(';')

This code takes a hash of HTTP headers, extracts all the keys, converts them to lower case strings before sorting and joining them (this is part of the process for creating the signature for AWS requests). Each step is done by an instance method of the previous piece of data.

In Elixir things don’t work this way: instead of doing some_hash.map {...} you do Enum.map(some_hash, ...), so the most naive attempt at translating this code is horrible. You can go for an unwieldy nesting of method calls:

1
Enum.join(Enum.sort(Enum.map(Dict.keys(headers), &(String.downcase(to_string &1))), ";")

Not only is the reading order back to front from the order of application, but the second argument to join is miles away from the call to join itself.

There’s also the ponderous

1
2
3
4
keys = Dict.keys headers
downcased_keys = Enum.map keys, &(String.downcase(to_string &1))
sorted_keys = Enum.sort downcased_keys
Enum.join(sorted_keys, ";")

Luckily elixir’s pipeline operator |> comes to the rescue:

1
Dict.keys(headers) |> Enum.map(&(String.downcase(to_string &1))) |> Enum.sort |> Enum.join(";")

What the pipeline operator does is take the what’s on its left hand side and insert as the first argument of what’s on the right hand side (you can of course add regular non pipeline provided argurments, as done here with join). I find this wonderfully clear and explicit.

I’d read several similar examples of this and I’d got to the point where I thought this was at least as good as the method chaining that had served me so well.

Not just “as good as”

What I realised when porting some of my own code as an exercise is that this is more flexible than ruby’s method chaining because of the reliance that each step is an instance method of the previous result.

For example if the next step was to compute the digest of that string, in ruby I’d have to either use one of the ponderous or unwieldy alternatives above or add a digest method to string. While, the latter is practical for the most common tasks, it quickly gets out of hand. On the other hand, in elixir you just keep adding pipeline stages:

1
2
Dict.keys(headers) |> Enum.map(&(String.downcase(&1))) |> Enum.sort |> Enum.join(";") |>
          digest() |> Base.encode16()

Where digest is a locally defined function that computes a digest (just a wrapper around :crypto.digest) and Base is the elixir module that provides base 16/32/64 functionality.

While occasionally you might need a little (possibly anonymous) wrapper function so that the injected argument is the first one, this doesn’t require polluting the namespace in the way that adding a digest method to ruby’s String class would while still retaining the ability to make any composition of functions you require.