How do I pass guards through macros?

Suppose you’ve got the following macro, which generates a function:

defmodule MyMacros do
  defmacro my_macro(param_1, param_2, do: body) do
    quote do
      def my_fun(
            unquote(param_1) = evaluated_param_1,
            unquote(param_2) = evaluated_param_2
          ) do
        IO.inspect(evaluated_param_1)
        IO.inspect(evaluated_param_2)

        unquote(body)
      end
    end
  end
end

Now, you can generate a function with it, e.g.:

defmodule MyFunctions do
  import MyMacros

  def my_fun(param_1, param_2)

  my_macro(a, b) do
    a + b
  end
end

And then I want to generate a function which has a guard, something like this (which doesn’t compile of course):

defmodule MyFunctions do
  import MyMacros

  def my_fun(param_1, param_2)

  my_macro(a, b) when a > 10 do
    IO.puts("I'm here!")
  end
end

I suspect I should somehow pass the guard similarly as the “do” block is passed through the last arg in keyword list, but I’m not sure how to do this (or whether it’s done differently).

Any hints? :thinking:

This one requires a few more parenthesis to explain:

def my_fun(a, b) when a > 10 do
  …
end

boils down to:

def(my_fun(a, b) when a > 10, do: …)

or in a more LISPy pseudo syntax:

def(when(my_fun(a, b), >(a, 10)), do: …)

The macro here is def/2 and the first argument is the whole function head. You’d essentially need to do the same – make the (a, b) of my_macro(a, b) no longer be the parameters of the macro, but of the AST of the function head until the do. This could for example be my_macro do(a, b) when a > 10 do.

You can see this often used with macros, which try to mimic syntax like with (which itself is a special form) e.g. in stream_data:

check all int1 <- integer(),
          int2 <- integer(),
          int1 > 0 and int2 > 0,
          sum = int1 + int2 do
  assert sum > int1
  assert sum > int2
end

which is essentially this:

check(all(…), do: …)
2 Likes

Thanks @LostKobrakai, I went with something along these lines:

  defmacro my_macro({:when, _, [{:_, _, [param_1, param_2]}, guard]}, do: body) do
    quote do
      def my_fun(
            unquote(param_1) = evaluated_param_1,
            unquote(param_2) = evaluated_param_2
          ) when unquote(guard) do
        IO.inspect(evaluated_param_1)
        IO.inspect(evaluated_param_2)

        unquote(body)
      end
    end
  end

which is used like this:

  my_macro _(a, b) when a > 10 do
    IO.puts("I'm here!")
    a * b
  end