Эликсир - динамический вызов частной функции
Я нашел Kernel.apply/3
, который позволяет динамически вызывать открытый метод в модуле, указав метод как атом, например. result = apply(__MODULE__, :my_method, [arg])
переводится на result = my_method(arg)
Что меня озадачивает - это способ вызова частного метода; Данный код выглядит следующим образом:
defmodule MyModule do
def do_priv(atom, args) when is_list(args) do
apply(__MODULE__, atom, args)
end
# (change defp => def, and this all works)
defp something_private(arg), do: arg #or whatever
end
Я ожидал бы, что MyModule.do_priv(:something_private, [1])
допустимо, так как это вызов частного метода изнутри модуля. Я могу оценить, что под капотом Elixir использует Erlang apply/3, и поэтому этот подход, вероятно, не приведет нас туда.
Я также пробовал использовать метод Code.eval_quoted/3
, но он даже не способен вызывать жесткокодированный частный метод (и, следовательно, время, затрачиваемое на создание AST вручную, вместо использования quote do
как ниже - хотя это вариант, если кто-то видит, как это сделать):
defmodule MyModule do
def do_priv_static do
something_private(1) #this works just fine
end
def do_priv_dynamic do
code = quote do
something_private(1)
end
Code.eval_quoted(code, [], __ENV__) #nope. fails
end
defp something_private(arg), do: arg #or whatever
end
Опять же, он получает доступ к частной функции изнутри содержащего модуля, поэтому я ожидаю, что это будет допустимо. Возможно, что я просто не понимаю параметр __ENV__
для eval_quoted
Единственное рабочее решение прямо сейчас меняет defp
на def
, что является прекрасным решением для моего личного кода; но поскольку я пишу код, который поддерживает других программистов, которые заботятся, я бы хотел найти решение.
Я открыт для других подходов, но лично я не понимаю, как это сделать.
Ответы
Ответ 1
Сначала вы должны знать, что f()
, вызываемый внутри модуля MyModule, - это не то же самое, что MyModule.f()
, вызываемое в том же месте. См. http://www.erlang.org/doc/reference_manual/code_loading.html#id86422
Вы можете использовать только частные функции f()
. Эти вызовы также проверяются компилятором - если функция не существует, вы получаете ошибку компиляции. Когда вы используете MyModule.f()
в одном и том же месте, вы не получаете ошибку компиляции, потому что эти вызовы проверяются только во время выполнения (даже если вы вызываете модуль из себя), и эффект (AFAIK) аналогичен тому, как если бы вы были вызывая MyModule.f()
из любого другого модуля - модуль просматривается во время выполнения, и вы можете вызывать только экспортированные (общедоступные) функции.
Поэтому вы не можете вызывать частные функции любым другим способом, кроме простого f()
. apply(mod,fun,[])
эквивалентен стилю mod.fun.()
- модуль разрешен во время выполнения, а частные функции недоступны.
Вы можете попробовать все варианты самостоятельно в этом примере: https://gist.github.com/mprymek/3302ff9d13fb014b921b
Теперь вы можете видеть, что вызовы частных функций всегда должны быть известны во время компиляции, поэтому вы не можете использовать даже волшебство eval_quoted или любую другую магию, чтобы сделать их "динамическими"...
Рекомендация Sasa Juric по использованию @doc false
- правильное решение.
Ответ 2
AFAIK динамически вызывает частные функции в Erlang (и, следовательно, не в Elixir). Если вам нужно выполнить динамическую отправку, вы можете рассмотреть возможность использования функции с несколькими предложениями. Надуманный пример (конечно, плохой, но не могу придумать лучший банкомат):
iex(1)> defmodule Math do
def operation(op) do
IO.puts "invoking #{inspect op}"
run_op(op)
end
defp run_op({:add, x, y}), do: x + y
defp run_op({:mul, x, y}), do: x * y
defp run_op({:square, x}), do: x * x
end
iex(2)> Math.operation({:add, 1, 2})
invoking {:add, 1, 2}
3
iex(3)> Math.operation({:mul, 3, 4})
invoking {:mul, 3, 4}
12
iex(4)> Math.operation({:square, 2})
invoking {:square, 2}
4
Еще одна альтернатива - сделать вашу функцию общедоступной, но указать с помощью @doc false
, что они являются внутренними, т.е. не предназначены для публичного использования клиентами. Вы также можете рассмотреть возможность перемещения таких функций в отдельный модуль и пометить весь модуль @moduledoc false
как внутренний. Оба подхода иногда используются в коде Elixir.
Однако я бы предложил начать простое и использовать функции сопоставления шаблонов + multi-clause. Если код становится более сложным, я бы рассмотрел другие варианты.
Ответ 3
Использование макросов
Вы можете использовать Macros
для динамического вызова частных методов в том же модуле. Вот простой макрос, который выполняет это:
defmacro local_apply(method, args) when is_atom(method) do
quote do
unquote(method)(unquote_splicing(args))
end
end
Чтобы вызвать его в своем модуле, вы можете сделать это (просто не забудьте определить макрос перед вызовом!):
def call_priv(some_argument) do
local_apply(:something_private, [some_argument])
end
defp something_private(arg), do: arg
local_apply
расширяется до нужного вам вызова метода с аргументами при вызове - но только во время компиляции - это означает, что вы не можете динамически расширять макрос до вызовов вашей функции во время выполнения.