1

I am trying to define dynamically functions inside defmacro but can’t understand why function value is unavailable inside function itself

defmacro __using__(_) do
  Enum.each ~w(public private), fn value ->
    def unquote(:"make_#{value}")(user = %User{}) do
       %{user | privacy: value}
    end
  end
end

Elixir expands by default value to value() and then says that there is no such function

1 Answer 1

1

You're missing a quote around the def. You also need to unquote the value in the Map update expression inside the def. And lastly you need to use Enum.map here instead of Enum.each so that __using__/1 returns the AST constructed:

defmacro __using__(_) do
  Enum.map ~w(public private), fn value ->
    quote do
      def unquote(:"make_#{value}")(user = %User{}) do
        %{user | privacy: unquote(value)}
      end
    end
  end
end

Test:

defmodule User do
  defstruct [:privacy]
end

defmodule A do
  defmacro __using__(_) do
    Enum.map ~w(public private), fn value ->
      quote do
        def unquote(:"make_#{value}")(user = %User{}) do
          %{user | privacy: unquote(value)}
        end
      end
    end
  end
end

defmodule B do
  use A
end
iex(1)> %User{} |> B.make_public
%User{privacy: "public"}

Edit: Change requested in comments:

defmacro __using__(_) do
  Enum.map ~w(public private), fn value ->
    quote do
      def unquote(:"make_#{value}")(user = %User{}) do
        %{user | privacy: unquote(value)}
      end
      def unquote(:"make_#{String.upcase(value)}")(user = %User{}) do
        %{user | privacy: unquote(String.upcase(value))}
      end
    end
  end
end
iex(1)> %User{} |> B.make_public
%User{privacy: "public"}
iex(2)> %User{} |> B.make_PUBLIC
%User{privacy: "PUBLIC"}
iex(3)> %User{} |> B.make_private
%User{privacy: "private"}
iex(4)> %User{} |> B.make_PRIVATE
%User{privacy: "PRIVATE"}
Sign up to request clarification or add additional context in comments.

6 Comments

But what if I need to add more functions to the same level as dynamically generated def?
You can put any number of def inside the quote.
The issue is that __using__ functions expects an AST list of definitions, so if I declare multiple quote then only the last one is taken.
I'm not sure I follow. Does my edited answer do what you want?
@Dogbert A quick question: using Enum.map the returned value is a list of quoted function definition. This is inserted into the calling module. Does it define functions? Quoted list is the list itself, but does it mean that compiler executes each element, so functions are defined?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.