I've found Kernel.apply/3
which allows dynamically calling a public method in a module by specifying the method as an atom, e.g. result = apply(__MODULE__, :my_method, [arg])
translates to result = my_method(arg)
What baffles me is a way to call a private method; Given code like this:
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
I would expect that MyModule.do_priv(:something_private, [1])
to be permissible, since it's a call to a private method from within the module. I can appreciate that under the hood Elixir is using Erlang's apply/3, and so this approach probably isn't going to get us there.
I've also tried using the Code.eval_quoted/3
method, but it doesn't even seem to be capable of calling a hardcoded private method (and hence no time spent building the AST by hand, rather than using quote do
as below- though that's an option if someone sees how to make this work):
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
Again, it's access to a private function from within the containing module, so I would expect it to be permissible. Its possible that I just don't understand the __ENV__
parameter to eval_quoted
The only working solution right now is changing defp
to def
, which is a fine solution for my personal code; but since I write code that supports other programmers who do care, I'd like to find a solution.
I'm open to other approaches, but I'm personally stumped on how to make this happen.
At first, you should know that f()
called inside a MyModule module is not the same thing as MyModule.f()
called in the same place. See http://www.erlang.org/doc/reference_manual/code_loading.html#id86422
You can call private functions only f()
style. These calls are also checked by the compiler - if the function does not exist, you get compile error. When you use MyModule.f()
in the same place, you do not get compile error because these calls are checked at runtime only (even if you are calling the module from within itself) and the effect is (AFAIK) the same as if you were calling MyModule.f()
from any other module - the module is looked up in runtime and you can call only exported (public) functions.
Therefore you can not call private functions in any other means than just a plain f()
. apply(mod,fun,[])
is an equivalent to mod.fun.()
style - the module is resolved at runtime and private functions are not accessible.
You can try all the variants by yourself in this example: https://gist.github.com/mprymek/3302ff9d13fb014b921b
You can see now that calls to private functions must always be known at the compilation time, so you can't use even eval_quoted magic or any other magic to make them "dynamic"...
Sasa Juric's advice to use @doc false
is the right solution.
AFAIK dynamically invoking private functions is not possible in Erlang (and therefore not in Elixir). If you need to do a dynamic dispatch, you could consider using a multi-clause function. A contrived example (surely a bad one, but can't think of a better ATM):
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
Another alternative is to make your function public, but indicate with @doc false
that they're internal - i.e. not meant to be used publicly by clients. You can also consider moving such functions to a separate module, and mark the whole module with @moduledoc false
as internal. Both approaches are occasionally used in Elixir code.
I would however suggest starting simple, and use pattern matching + multi-clause functions. If the code becomes more complex, I would then consider other options.
You can use Macros
to dynamically call private methods within the same Module. Here's a simple macro that accomplishes that:
defmacro local_apply(method, args) when is_atom(method) do
quote do
unquote(method)(unquote_splicing(args))
end
end
To call it in your module you can do this (Just remember to define the macro before calling it!):
def call_priv(some_argument) do
local_apply(:something_private, [some_argument])
end
defp something_private(arg), do: arg
local_apply
would expand to your desired method call with arguments when called - but only at compile time - that means you can't dynamically expand the macro to your function calls at runtime.
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