Defining Function clauses in macros across multiple quote blocks

I’ve stumbled on some surprising behavior defining function in a macro and wondering if anyone can shed light on whether this is expected behavior.

The following snippet should be able to explain the issue best:

defmodule Lookup1 do
  defmacro __using__(_opt) do
    quote do
      def lookup("a"), do: true
      def lookup("b"), do: true
    end
  end
end

defmodule Lookup2 do
  defmacro __using__(_opt) do
    quote do
      def lookup("a"), do: true
    end

    quote do
      def lookup("b"), do: true
    end
  end
end

defmodule UseLookup1 do
  use Lookup1

  def test do
    IO.inspect(lookup("a"), label: "Lookup1 a")
    IO.inspect(lookup("b"), label: "Lookup1 b")
  end
end

defmodule UseLookup2 do
  use Lookup2

  def test do
    IO.inspect(lookup("a"), label: "Lookup2 a")
    IO.inspect(lookup("b"), label: "Lookup2 b")
  end
end

UseLookup1.test()
UseLookup2.test()

The UseLookup1.test() works fine and prints ‘true’ for both.

The UseLookup2.test() call, however fails with a FunctionClauseError:

** (FunctionClauseError) no function clause matching in UseLookup2.lookup/1

    The following arguments were given to UseLookup2.lookup/1:

        # 1
        "a"

    test.ex:32: UseLookup2.lookup/1
    test.ex:35: UseLookup2.test/0

Should I be able to define function clauses across multiple quote blocks?

I am able to call UseLookup2.lookup(“b”) so it seems that the def in the second quote ends up overwriting the function clauses in the first.

Yes, however you are not doing that, your UseLookup2 is only returning the second quote block, you need to return both, like in a new block:

defmodule Lookup2 do
  defmacro __using__(_opt) do
    a = quote do
      def lookup("a"), do: true
    end

    b = quote do
      def lookup("b"), do: true
    end

    quote do
      unquote(a)
      unquote(b)
    end
  end
end

Remember that only the last expression is returned.

3 Likes

Thanks! I see I hadn’t quite wrapped my mind around what defmacro was doing. This clarifies things :slight_smile:

defmacro is just a def except it runs at compile-time instead of run-time, that’s the only difference, other than that it is just a normal function that takes in ast arguments and is expected to return ast. :slight_smile:

3 Likes