Eiji

Eiji

Please help me with macro for generating functions

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.

Marked As Solved

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.

Also Liked

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
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.

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?

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
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.”

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
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
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics 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
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New

We're in Beta

About us Mission Statement