1
votes

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

1 Answers

0
votes

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"}