1

I am playing around with Elixir macros - specifically macros that call themselves which is something that I do often in Scheme. I have created a little test macro below however it just hangs iex - nothing is printed to console. Does anyone have any insight into why and what could be done to correct it?

defmodule RecMac do
  defmacro test_rec(x) do
    quote do
      IO.puts("Started?")
      if(unquote(x) < 1) do
        IO.puts("Done?")
        "done"
      else
        IO.puts("Where are we")
        IO.puts(unquote(x))
        RecMac.test_rec(unquote(x) - 1)
      end
    end
  end
end

EDIT!!

OK, so it turns out you can define recursive macros where there is a structural difference to match on (eg lists). The following is working for me. And to confirm @Aleksei Matiushkin below, the above will not work and does indeed not work in scheme!

  defmacro test_rec([h | t]) do
    quote do
      IO.inspect([unquote(h) | unquote(t)])
      RecMac.test_rec(unquote(t))
    end
  end

  defmacro test_rec([]) do
    quote do
      IO.puts "Done"
    end
  end
end

I am very happy to have dug into this as I learned something about two languages!

2 Answers 2

3

TL;DR: this is impossible.


Macros in are not what you expect them to be. When a compiler sees a macro, it calls it during a compilation time and injects the AST it returned in the place of where it was called. That said, recursive macros would always lead to the infinite loop at the compilation stage.

Put IO.puts("something") before quote do instruction and you’ll see it to be printed infinitely, once per subsequent call to expand the macro.

You can use @compile {:inline, test_rec: 1} to achieve the behaviour you are after. For better understanding macros, you probably should read Macros section in the Elixir Guide in general and this excerpt in particular:

Macros are harder to write than ordinary Elixir functions and it’s considered to be bad style to use them when they’re not necessary. So write macros responsibly.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer, fair enough that it is not possible in Elixir, but it is certainly possible in Scheme which makes me think this is a shortcoming in Elixir macros :-(
scheme is not compiled language in the first place (things like chicken should be called transpilers.) Elixir compiles into BEAM and macros were invented to simplify traversing AST. It’s a feature, not a shortcoming by any means. Feel free to call them ast-injectors instead of macros if you feel that macros should always be like those in scheme.
github.com/cisco/chezscheme is certainly compiled. not here to argue though :-) thanks again for your answer - not being able to do thiis in Elixir is not a deal breaker for me, the language is very productive in other ways and it's fun.
3

Actually you can do kind of recursion, but point is to think what you are doing to avoid calling macro inside quote do [....] end. Your example with IO.puts would be something like this

defmodule RecMac  do
  defmacro test_rec(list) do
    {:__block__, [], quote_value(list)}
  end

  defp quote_value([]), do: []
  defp quote_value([h | t]) do
    if h < 1 do
      []
    else
      ast = quote do
         IO.puts("#{unquote(h)}")
      end
      [ast | quote_value(t)]
    end
  end
end

you will notice two things, I do use recursion in macro but outside quote using private function quote_value/1 and that function has logic that "stops" after it finds value lower than 1. All "quotes" are put into list and trick is to put this list into tuple {:__block__, [], put_quote_list_here}

Now note that this macro wont compile if list parameter of test_rec is not know upfront (during compile time), so you need to call macro test_rec(["a", "b", 0, 100, 200]) so compiler knows size and elements of that list.

BTW, I used body optimized recursion, but you can easily add accumulator and convert this into tail optimized recursion.

3 Comments

he better not use this at all, imo.
@Milan Jaric Thanks for a very interesting piece of code to ponder! I'm just seeing what is and isn't possible with Elixir macros so this is great :-)
@Daniel I agree because it has no difference at all than just calling quote_value directly at run time. But if you have something more complex, e.g. if you want to build DSL for some domain then it has sense to do something like this, and instead of usin IO.puts, just add code that should do work instead of DSL keywords

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.