I'm working my way through Dave's upcoming book on Elixir, and in one exercise I would like to dynamically construct a function reference to Kernel.+/2
, Kernel.-/2
etc, based on the contents of one character of a string, '+'
, '-'
and so on.
Based on another SO question I expected to be able to call apply/3
passing Kernel, :+ and the two numbers like this:
apply(Kernel, :+, [5, 7])
This doesn't work because (if I understand right) Kernel.+/2
is a macro, not a function. I looked up the source code, and +
is defined in terms of __op__
, and I can call it from iex
:
__op__(:+, 5, 7)
This works until I put the :+ into a variable:
iex(17)> h = list_to_atom('+')
:+
iex(18)> __op__(h, 5, 7)
** (CompileError) iex:18: undefined function __op__/3
src/elixir.erl:151: :elixir.quoted_to_erl/3
src/elixir.erl:134: :elixir.eval_forms/4
And I'm guessing there's no way to call __op__
using apply/3
.
Of course, the brute-force method gets the job done.
defp _fn(?+), do: &Kernel.+/2
defp _fn(?-), do: &Kernel.-/2
defp _fn(?*), do: &Kernel.*/2
# defp _fn(?/), do: &Kernel.//2 # Nope, guess again
defp _fn(?/), do: &div/2 # or &(&1 / &2) or ("#{div &1, &2} remainder #{rem &1, &2}")
But is there something more concise and dynamic?
José Valim nailed it with his answer below. Here's the code in context:
def calculate(str) do
{x, op, y} = _parse(str, {0, :op, 0})
apply :erlang, list_to_atom(op), [x, y]
end
defp _parse([] , acc ) , do: acc
defp _parse([h | t], {a, b, c}) when h in ?0..?9, do: _parse(t, {a, b, c * 10 + h - ?0})
defp _parse([h | t], {_, _, c}) when h in '+-*/', do: _parse(t, {c, [h], 0})
defp _parse([_ | t], acc ) , do: _parse(t, acc)
You can just use the Erlang one:
apply :erlang, :+, [1,2]
We are aware this is confusing and we are studying ways to make it or more explicit or more transparent.
UPDATE: Since Elixir 1.0, you can dispatch directly to Kernel (apply Kernel, :+, [1, 2]
) or even use the syntax the OP first attempted (&Kernel.+/2
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With