Я работаю над предстоящей книгой Дейва об Эликсире, и в одном упражнении я хотел бы динамически создать ссылку на функцию для Kernel.+/2
, Kernel.-/2
и т. д., основываясь на содержимом одного символа строки, '+'
, '-'
и т. д.
Основываясь на другом вопросе SO, я ожидал, что смогу вызвать apply/3
, передавая ядро, :+ и два числа, подобные этому:
apply(Kernel, :+, [5, 7])
Это не работает, потому что (если я правильно понимаю) Kernel.+/2
— это макрос, а не функция. Я посмотрел исходный код, и +
определяется в терминах __op__
, и я могу вызвать его из iex
:
__op__(:+, 5, 7)
Это работает, пока я не помещу :+ в переменную:
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
И я предполагаю, что нет никакого способа вызвать __op__
, используя apply/3
.
Конечно, метод грубой силы делает свою работу.
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}")
Но есть ли что-то более лаконичное и динамичное?
Хосе Валим прибил его своим ответом ниже. Вот код в контексте:
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)