vshesh
Composing macros (using a macro inside another macro)
I’m trying to use one macro inside another macro and running into issues:
defmodule Test do
defmacro somemacro(arg1, do: block) do
quote do
defmodule unquote(arg1) do
def unquote(arg1), do: 1
unquote_splicing(elem(block, 2))
end
end
end
defmacro othermacro(arg2, arg1, do: block) do
__MODULE__.somemacro arg1, do: quote do
def unquote(arg2), do: 2
end
end
end
I get that I can’t unquote inside othermacro so how do I do this?
Another try:
defmodule Test do
defmacro somemacro(arg1, do: block) do
quote do
defmodule unquote(arg1) do
def unquote(arg1), do: 1
unquote_splicing(elem(block, 2))
end
end
end
defmacro othermacro(arg2, arg1, do: block) do
newblock = quote do
def unquote(arg2), do: 2
end
__MODULE__.somemacro arg1, do: newblock
end
end
ERROR: you must require Test before invoking the macro Test.somemacro/2
requiring Test within Test causes another error:
(CompileError) you are trying to use the module AEnum which is currently being defined.
This may happen if you accidentally override the module you want to use. For example:
defmodule MyApp do
defmodule Supervisor do
use Supervisor
end
end
In the example above, the new Supervisor conflicts with Elixir's Supervisor. This may be fixed by using the fully qualified name in the definition:
defmodule MyApp.Supervisor do
use Supervisor
end
Basically othermacro is the same as somemacro except it adds some extra functions to the module
I am trying to use the do block to make a composition of the bits of the module … any help welcome
Most Liked
al2o3cr
Removing the __MODULE__. from othermacro fixes the require issue, but exposes a bigger problem:
defmodule Test do
defmacro somemacro(arg1, do: block) do
quote do
defmodule unquote(arg1) do
def unquote(arg1), do: 1
unquote_splicing(elem(block, 2))
end
end
end
defmacro othermacro(arg2, arg1, do: block) do
newblock = quote do
def unquote(arg2), do: 2
end
somemacro arg1, do: newblock
end
end
which fails with:
** (ArgumentError) expected a list with quoted expressions in unquote_splicing/1, got: nil
(elixir 1.11.2) src/elixir_quote.erl:185: :elixir_quote.argument_error/1
(elixir 1.11.2) src/elixir_quote.erl:166: :elixir_quote.list/2
This happens because when othermacro calls somemacro, the value somemacro sees in block is {:newblock, [line: 17], nil}.
One way to avoid this problem is to keep the defmacro parts simple and depend on plain functions to generate the actual AST fragments:
defmodule Test do
defmacro somemacro(arg1, do: block) do
# wrapped block in a list because unquote_splicing expects a list of quoted expressions
build_ast(arg1, [block])
end
defmacro othermacro(arg2, arg1, do: block) do
newblock = quote do
def unquote(arg2)(), do: 2
end
# the old code didn't use block (?)
build_ast(arg1, [block, newblock])
end
defp build_ast(arg1, block) do
quote do
defmodule unquote(arg1) do
def unquote(arg1)(), do: 1
unquote_splicing(block)
end
end
end
end
defmodule FooMod do
require Test
Test.othermacro(:foo, :bar, do: "not used")
end
BUT BEWARE SOME GOTCHAS
The module defined by Test.somemacro is not nested - so in the example, it generates a top-level module named :bar:
iex(26)> :"Elixir.FooMod.bar".foo
** (UndefinedFunctionError) function :"Elixir.FooMod.bar".foo/0 is undefined (module :"Elixir.FooMod.bar" is not available)
:"Elixir.FooMod.bar".foo()
iex(26)> :bar.foo
2
This may not be what you want.







