git / email
index

Curried Elixir

Elixir already supports partially applied functions thanks to the capture operator, and partial application is nice. But you know what's even better? Currying!

Let's say we define this function:

def my_fun(a, b, c) do
  a + b + c
end

What we would want is to have a curry/1 function that would allow us transforming this function into a curried function:

iex> curried_fun = curry(&my_fun/3)
iex> curried_fun.(1).(2).(3)
6
iex> add_42 = curried_fun.(40).(2)
iex> add_42.(10)
52

We're going to do this by wrapping my_fun in a chain of anonymous functions, each binding exactly one name:

curried_fun =
  fn (a) ->
    fn (b) ->
      fn (c) ->
        my_fun.(a, b, c)
      end
    end
  end

The most (and only?) important thing we need to know in order to implement this curry function is the arity of the function we are given. We can use Function.info/2 for this:

iex> Function.info(fn x, y -> x * y end, :arity)
{:arity, 2}

Now that we have this, let's write this curry function!

defmodule Func do
  def curry(fun) when is_function(fun) do
    {:arity, arity} = Function.info(fun, :arity)
    curry(fun, arity, [])
  end

  def curry(fun, 0, []), do: fn -> fun.() end
  def curry(fun, 1, args), do: fn x -> apply(fun, Enum.reverse([x | args])) end
  def curry(fun, arity, args), do: fn x -> curry(fun, arity - 1, [x | args]) end
end

And that's it! Here's what happens when we're using curry:

iex> f = curry(&Enum.map/2)
#Function<...> # fn x -> curry(&Enum.map/2, 1, [x]) end
iex> f.(1..3)
#Function<...> # fn x -> apply(&Enum.map/2, Enum.reverse([x, 1..3])) end
iex> f.(&"i = #{&1}")
["i = 1", "i = 2", "i = 3"] # apply(&Enum.map/2, [1..3, &"i = #{&1}"])

0-arity functions are a special case, as we don't want curry to apply them for us, i.e.:

iex> curry(&self/0)
#PID<...> # apply(&self/0, [])

Instead, we wrap the function in a 0-arity anonymous function:

iex> f = curry(&self/0)
#Function<...> # fn -> apply(&self/0, []) end
iex> f.()
#PID<...> # apply(&self/0, [])