Defining Function clauses in macros across multiple quote blocks

macros

#1

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.


#2

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

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


#4

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: