Invoke a private macro within a quote block with a variable

I’m trying to invoke a private macro, within a quote block, using a variable defined within the code block itself.
Notice that the variable doesn’t need to be evaluated, I’m thinking of the macro in terms of search-and-replace:

  • Find the debug keyword, replace it with IO.puts(my)

This is the pseudo-code showing what I would like to do (doesn’t work)

defmodule Foo do
  defmacrop debug(msg) do
    quote bind_quoted: [msg: msg], do: IO.puts(msg)
  end

  defmacro __using__(_) do
    quote do
      def hello do
        my = "testme"

        unquote(debug(quote do: my))
      end
    end
  end
end

defmodule Bar do
  use Foo
end

Bar.hello()

And the end result would be pretty simple:

defmodule Bar do
  def hello do
    my = "testme"
    IO.puts(my)
  end
end

I’ve been struggling with this for 3 days now

1 Like

The usual way I would expect this to be done is as follows

defmodule Foo do
  defmacro debug(msg) do
    quote do
      IO.puts(unquote(msg))
    end
  end

  defmacro __using__(_) do
    quote do
      def hello do
        my = "testme"

        Foo.debug(my)
      end
    end
  end
end

defmodule Bar do
  use Foo
end

Bar.hello()

You will note that the debug macro is not private. The reason is simple, the Foo.__using__ macro in this case is being used to write a function in the Bar module: Bar.hello. For a function in Bar to call a macro (or function) in Foo that function must be public.

3 Likes

Mhhh that’s very different result though. My purpose was to actually keep the macro private because it’s an internal utility of the module itself. I’m strictly looking for a way to do it with a private macro that is not included in the module “using” the other one (to avoid overlapping names).

Thanks for the suggestion though. I’ll consider it

If the macro is private to Foo then it can only ever be called from functions or other macros that are also in Foo - the very definition of private.

The concept of “a private macro that is not included in the module using the other one” simply does not make sense. To call a private macro from Bar the macro will have to be defined in Bar.

Overall, in my opinion, it looks like you are trying to replicate the object oriented constructs of public, protected, and private inheritance which suggests to me that you’re going about things the wrong way.

Perhaps if you offered more details about what you are trying to accomplish?

1 Like

@easco is right. I would just add @doc false to it, which effectively means public to the library, private to users of the library.

1 Like

Ok thanks, I guess I’ll do that way.

For note, don’t make debug/1 be a macro, you are already in a macro context and you are wanting to inject AST, not the result of such AST:

defmodule Foo do
  defp debug(msg) do
    quote bind_quoted: [msg: msg], do: IO.puts(msg)
  end

  defmacro __using__(_) do
    quote do
      def hello do
        my = "testme"

        unquote(debug(quote do: my))
      end
    end
  end
end

defmodule Bar do
  use Foo
end

Bar.hello()
2 Likes

Ok that’s perfect and solves my problem.
It also opens my eyes a lot based on your description: “you want to inject ast”

Thanks a lot, I wasted so much time on it yesterday!

1 Like