Eiji

Eiji

Hi, I looked about generating functions and found one example. I tried to make it work inside macro, but i don’t know how I can fix it. Here is sample code that I need help with:

defmodule FirstExample do
  Enum.each [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
    def unquote(func_name)(unquote_splicing(args)), do: :ok
  end
end

defmodule SecondExample do
  defmacro my_macro(name) do
    quote do
      defmodule unquote(name) do
        Enum.each [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
          def unquote(func_name)(unquote_splicing(args)), do: :ok
        end
      end
    end
  end
end

Please explain me:

  1. How unquote works in FirstExample?
    unquote should work only inside of quote, right?
  2. How I could correct SecondExample to make it work?
    Important: I need these data to be inside defmodule unquote(name) do ... end and I need to generate whole module)?
    I can see that unquote tries to get data outside of quote, but i don’t know how I can fix it to make it work like in FirstExample.

Showing Posts 1 to 9

mudasobwa

mudasobwa

Creator of Cure

Use Module.create/3 that, as by documentation, “Creates a module with the given name and defined by the given quoted expressions.”

defmodule SecondExample do
  defmacro my_macro(name) do
    contents =
      quote do
        Enum.map [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
          quote do: def unquote(func_name)(unquote_splicing(args)), do: :ok
        end
      end
    quote bind_quoted: [name: name, contents: contents] do
      Module.create(name, contents, Macro.Env.location(__ENV__))
    end
  end
end

defmodule Test do
  require SecondExample
  def test, do: IO.inspect SecondExample.my_macro(Foo), label: "Module content"
end

Test.test
Eiji

Eiji OP

@mudasobwa: Thank you. It’s really helpful, but I still can’t get it work with my code

I have 3 problems:

  1. If I place 3 times similar each call (with another function names) then only last is added as module body (I can’t run other functions in iex). I want to pass whole module body there - not only last result of quote.
  2. After change order nothing happens until I remove _build folder and compile whole project again - this is really uncomfortable.
  3. In my case there is another problem: I have stored list of that data (function names and arguments) in module attribute and i would like to keep them there.

The workaround for 1st problem:

# ...
contents = quote do
  [quote do
    # first part, define and work on module attributes and functions
  end]
  ++
  Enum.map(...) # generated functions here ...
end
# ...

but I think it’s not a best idea.

I will try to describe better how my code works. In my version these looks like:

defmodule MyLib do
  defmacro my_macro(name, do: block) do
    quote do
      defmodule unquote(name) do
        # alias, import and require here ...

        # pre work: attributes + define some methods

        _ = unquote(block)

        # post work: also attributes + define some methods

        # call macro that returns this list - IO.inspect here shows correct data,
        # but here starts my problem, because I can't use unquote here
        # and I can't use unquote_splicing if I try to place `each` call in separate macro
        # it's something like:
        result_of_my_macro = Example.parse_data(@my_data)
        Enum.each result_of_macro, fn {func_name, args} ->
          def unquote(func_name)(unquote_splicing(args)), do: :ok
        end
        # unfortunately I can't use your version (it looks really good),
        # because I have data for Enum.each inside module body
        # i.e. in same scope as definition of that functions
      end
    end
  end
end

# There are much more, but I hope that you now better understand my problem

# in another file:
require MyLib
MyLib.my_macro ModuleName do
  # here I use some macro dynamically imported into this module body
end

What do you think about it?

gregvaughn

gregvaughn

I’m no macro expert, but don’t you want to be using Enum.map instead of Enum.each so you can capture the generated AST?

Eiji

Eiji OP

@gregvaughn: Originally I’m using Enum.each. I used Enum.map only to test @mudasobwa answer.

Simply in defmodule -> Enum.each works without error.

There is a problem with args, because I can’t call unquote_splicing inside defmodule -> defmacro -> quote -> Enum.each.

Please try SecondExamplecode in my first post for example in iex.

mudasobwa

mudasobwa

Creator of Cure

Don’t prepend the element to the list with ++, use [head | tail] notation:

contents = quote do
  [
    quote do
      # first part, define and work on module attributes and functions
    end | 
    Enum.map(...) # generated functions here ...
  ]
end

You seem to misheard what I was saying: one should not use defmodule inside macros/functions due to the unquoting scopes problems you’ve met. Use Module.create/3 in such a case. There is no way (at least legit) to unquote from the middle level of nested quoting.

Whether you are still positive you want to violate guidelines and how-tos provided by core team members, you might use a hack (read: a kludge) with Code.eval_quoted/3 as shown below:

defmodule KludgeExample do
  defmacro my_macro(name) do
    quote do
      defmodule unquote(name) do
        Enum.each [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
          ast = quote do: def unquote(func_name)(unquote_splicing(args)), do: :ok
          Code.eval_quoted(ast, [func_name: func_name, args: args], __ENV__)
        end
      end
     end
  end
end

defmodule Test do
  require KludgeExample
  def test do
    KludgeExample.my_macro(Foo)
    IO.inspect {Foo.foo(42), Foo.bar}, label: "{Foo.foo(42), Foo.bar()}"
  end
end

Test.test

In my case there is another problem: I have stored list of that data (function names and arguments) in module attribute and i would like to keep them there.

I do not see any problem here: add the module attribute declaration to the contents as shown below:

contents =
  quote do
    [ quote do
        Module.put_attribute(__MODULE__, :my_data, ...)
        # first part, define and work on module attributes and functions
      end | 
      Enum.map(...) # generated functions here ...
    ]
  end
NobbZ

NobbZ

Appending here is totally fine. If not elixir does it, erlang will optimise it away. It will optimise appending to a singleton list into consing.

Also, there is often no problem with appending to a list once, it does get dangerous when done recursively though.

mudasobwa

mudasobwa

Creator of Cure

Yes, indeed, I should’ve put the better wording there. I meant “get a habit to use [head | tail] notation, rather than [head] ++ tail, it might save you time for not thinking whether it’s a performance hit or not.”

net

net

I’m guessing @my_data is set by the block. You can split large quote into two quotes, and use [unquote: false] to disable unquote/1. Consider moving the first quote to a private function in MyLib for better readability and maintainability.

defmodule MyLib do
  defmacro my_macro(name, do: block) do
    function_generator =
       quote unquote: false do
         for {name, args} <- Example.parse(@my_data) do
           def unquote(name)(unquote_splicing(args)), do: :ok
         end
       end

    quote do
      defmodule unquote(name) do
        unquote(block)
        unquote(function_generator)
      end
    end
  end
end

@gregvaughn Enum.each is fine in this case as def just inserts into an ets table.

Eiji

Eiji OP

@mudasobwa: Thanks, but these does not look good (or another words: these could look better), but your example is very, very good for simpler cases (your first example looks really nice).

I know [head | tail] notation, but this was only example - i.e. at end I will rewrite it anyway, but I don’t know about what @NobbZ say - really interesting, thanks!

You should agree with me that @net solution looks best for my case - i.e. there is no need to care about what quoted block returns which was exactly what I want. Just now I tested it with my code - it’s really little change in this case and it works as expected. For me it looks best, so I mark it as a answer.

Thank you all for your time and help!

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews