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

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New
Stef-Verniers
Hello I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews