vshesh

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

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.

Where Next?

Popular in Questions Top

bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement