aaronrenner
How do I write a macro that dynamically defines a public or private function?
I’m trying to write a couple macros that define either a public or private functions. Here’s an example
defmodule Defs do
defmacro mydef(name, do: block) do
quote do
def unquote(name), do: unquote(block)
end
end
defmacro mydefp(name, do: block) do
quote do
defp unquote(name), do: unquote(block)
end
end
end
# Example usage
mydefp hello do
:world
end
Since the functions that are being written are exactly the same except for def/defp, I’d like to extract the function writing logic into a common place
defmodule Defs do
defmacro mydef(name, do: block), do: do_def(name, block, :def)
defmacro mydefp(name, do: block), do: do_def(name, block, :defp)
defp do_def(name, block, visibility) do
#What goes here?
end
end
Since def is just a macro on Kernel, I rewrote my macros to look like they were writing function calls.
defmodule Defs do
defmacro mydef(name, do: block) do
quote do
Kernel.def(unquote(name), do: unquote(block))
end
end
defmacro mydefp(name, do: block) do
quote do
Kernel.defp(unquote(name), do: unquote(block))
end
end
end
From there, it seemed that I should be able to use apply/3 to dynamically call def or defp.
defmodule Defs do
defmacro mydef(name, do: block), do: do_def(name, block, :def)
defmacro mydefp(name, do: block), do: do_def(name, block, :defp)
defp do_def(name, block, visibility)
apply(Kernel, unquote(visibility), [unquote(name), do: unquote(block)])
end
end
However, when I use this macro in a module:
defmodule Greeter do
import Defs
mydef hello do
:world
end
I get the following error:
** (CompileError) iex:3: undefined function hello/0
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1355: :lists.mapfoldl/3
(my_app) expanding macro: Defs.mydef/2
Long question short, how do I dynamically call def or defp so I can share the rest of the logic between my macros?
Most Liked
mudasobwa
The main issue here is: one cannot unquote outside of the quote. That said, you cannot call unquote inside your private function. The good news would be, you don’t need it at all, just construct an AST:
defmodule Defs do
defmacro mydef(name, do: block),
do: do_def(name, block, :def)
defmacro mydefp(name, do: block),
do: do_def(name, block, :defp)
defp do_def(name, block, visibility) do
{{:., [], [{:__aliases__, [alias: false], [:Kernel]}, visibility]},
[], [name, [do: block]] }
end
end
defmodule Greeter do
import Defs
mydef hello do
:world
end
end
IO.puts Greeter.hello
#⇒ world
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









