tmbb
Error in macro that defines a module with a macro inside
I have some relatively legitimate reasons do do something like this (even if you think this is not even remotely legitimate, the question at the end is independent from this):
defmodule MySnippet do
defmacro __using__(_) do
quote do
def common_function1() do
# ...
end
def common_function2() do
# ...
end
# etc...
end
end
end
defmodule MyModule1 do
use MySnippet
def specific_function1() do
# ...
end
def specific_function2() do
# ...
end
# etc...
end
defmodule MyModule2 do
use MySnippet
def specific_function_a() do
# ...
end
def specific_function_b() do
# ...
end
# etc...
end
I’ve thought about defining a defsnippet macro that exapnds so that I coould do this:
require Snippet
Snippet.defsnippet MySnippet do
def f(x), do: x
def g(x), do: x
end
This way, it looks like a normal module definition and is easier to type.
I’ve written the following macro, which tries to encapsulate the module definition and the __using__/1 macro definition.
defmodule Snippet do
defmacro defsnippet(module_name, [do: body]) do
quote bind_quoted: [module_name: module_name, body: body] do
defmodule module_name do
@doc false
defmacro __using__(_opts) do
quote do
body
end
end
end
end
end
end
But when I try to run it like this
# lib/schism/example.ex
require Snippet
Snippet.defsnippet MySnippet do
def f(x), do: x
def g(x), do: x
end
I get this error:
== Compilation error in file lib/schism/example.ex ==
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:5142: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3905: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
lib/schism/example.ex:4: (file)
expanding macro: Snippet.defsnippet/2
lib/schism/example.ex:3: (file)
I don’t understand where this comes from, because I’m not calling def/2 outside of a module. I’m just taking some AST that happens to contain defs and putting it inside a macro which I can invoke later.
Doing all of this just to save some typing is probably not worth it and I don’t know if I’ll end up going that way, but no I’m curious about what the problem is independently of the rest.
Marked As Solved
OvermindDL1
Eh I think defmodule is fine here.
However the issue is that you cannot use bind_quoted in this way (I learned that well long ago). Basically what bind_quoted will do is turn the code into something like this from the return of your macro:
module_name = MySnippet
body =
(
def f(x), do: x
def g(x), do: x
)
defmodule(module_name) do
@doc false
defmacro(__using__(_opts)) do
quote do
body
end
end
end
And this is why you are getting the def is not allowed here message. I’ve always had to do ‘fun’ unquote messing when I had to do similar things. So if you Macro.escape the body in bind_quoted then use unquote(body) inside the function then it might work (you might need to ‘escape’ the inner quote though or something like that, I usually just don’t use bind_quoted at all in this case and just unquote through the layers as needed or build the AST manually without quote…).
Also Liked
tmbb
It doesn’t. In fact, defmodule doesn’t have anything to do with it. OverminDL1 has given the correct answer. Thanks for your input anyway!
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









