aaronrenner

aaronrenner

Phoenix Core Team

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

mudasobwa

Creator of Cure

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

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
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
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&query=perfume&page=2, I would like to get: ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
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
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42158 114
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

We're in Beta

About us Mission Statement