So while writing Confy, I wanted to turn shorthand atoms like:integer, :string, :atom into the ‘normal’ forms &Confy.Parsers.integer/1, &Confy.Parsers.string/1, &Confy.Parsers.atom/1, etc.
However, I struggled to properly perform quoting and unquoting while using the function capture operator&.
# Replaces simplified atom parsers with
# an actual reference to the parser function in `Confy.Parsers`.
# NOTE: I dislke the necessity of `Code.eval_quoted` here, but do not currently know of another way.
defp normalize_parser(parser) when is_atom(parser) do
case Confy.Parsers.__info__(:functions)[parser] do
nil -> raise ArgumentError, "Parser shorthand `#{inspect(parser)}` was not recognized. Only atoms representing names of functions that live in `Confy.Parsers` are."
1 ->
{binding, []} = Code.eval_quoted(quote do &Confy.Parsers.unquote(parser)/1 end)
binding
end
end
defp normalize_parser(other), do: other
Note the Code.eval_quoted line: Although it is probably safe since it is only executed when parser indeed is an atom and exists as a function inside the Confy.Parsers module, it does not feel nice to need it.
Is there another way to build a function capture out of a module and function atom?
Note that 'function captures like wrapping everything with fn are not what I am looking for, since the result of normalize_parser is also used to create documentation: inspect(&Module.foo/1) is "&Module.foo/1" whereas inspect(fn x -> Module.foo(x) end) is "#Function<6.127694169/1 in :erl_eval.expr/5>".
Would not &apply(Confy.Parsers, parser, [&1]) work?
apply/3 is so very useful. ^.^
iex(1)> h apply/3
def apply(module, function_name, args)
@spec apply(module(), function_name :: atom(), [any()]) :: any()
Invokes the given function from module with the list of arguments args.
apply/3 is used to invoke functions where the module, function name or
arguments are defined dynamically at runtime. For this reason, you can't invoke
macros using apply/3, only functions.
Inlined by the compiler.
## Examples
iex> apply(Enum, :reverse, [[1, 2, 3]])
[3, 2, 1]
Consequently it has no speed hit over a normal indirect call either since they lower to the same beam code, just to be sure I’ve also benchmarked it. ^.^
if function_exported?(Confy.Parsers, parser, 1) do
iex(2)> h function_exported?
def function_exported?(module, function, arity)
@spec function_exported?(module(), atom(), arity()) :: boolean()
Returns true if module is loaded and contains a public function with the given
arity, otherwise false.
Note that this function does not load the module in case it is not loaded.
Check Code.ensure_loaded/1 for more information.
Inlined by the compiler.
## Examples
iex> function_exported?(Enum, :member?, 2)
true