sheharyarn
Define functions in module metaprogrammically
I have three identical methods in my module, that do (almost) exactly the same thing. Instead of repeating the function definitions, I am trying to define them once to keep code minimal and same for all of them.
So far I’ve tried using Code.eval_string:
defmodule MyModule do
Enum.each ~w(method1 method2 method3), fn method ->
@method method
Code.eval_string """
def #{@method}(map) when is_map(map) do
do_map_stuff(:#{@method}, map)
end
def #{@method}(arg) do
do_other_stuff(:#{@method}, arg)
end
"""
end
## Other Methods
end
but this throws ArgumentError:
Compiling 1 file (.ex)
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:4297: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3299: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
nofile:1: (file)
I think quote/unquote might be the way to go, but I’m not exactly sure how to do this using them (I’ve already read the Meta Guide on the Elixir website).
Most Liked Responses
alxndr
Don’t need to use Code.eval_string; you can wrap a def in a “loop” and use unquote/1 to “extract” the variable into the context that you’re def-ing in:
defmodule M do
Enum.each ~w(foo bar), fn(method_name) ->
def unquote(:"#{method_name}")(), do: unquote(method_name)
end
end
vic
There’s no need to use eval_string, you could use Code.eval_quoted but it’d be much much better to just define a macro in another module and then import it.
Here’s a quick example, you can put on a file foo.ex and execute with elixir.
defmodule Gen do
# Note that macro arguments and return values are quoted expressions
defmacro gen({name, _, _}) do
quote do
# here we define it dinamically
def unquote(name)() do
IO.inspect(unquote(name))
end
end
end
end
defmodule Foo do
import Gen
gen foo
end
Foo.foo








