Рекурсивный макрос Эликсир - PullRequest
0 голосов
/ 10 февраля 2020

Я играю с макросами Elixir - в частности, с макросами, которые называют себя, что я часто делаю в Scheme. Я создал небольшой тестовый макрос ниже, но он просто зависает iex - ничего не выводится на консоль. Кто-нибудь знает, почему и что можно сделать, чтобы исправить это?

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

РЕДАКТИРОВАТЬ !!

ОК, так что получается, что вы можете определить рекурсивные макросы там, где есть структурные разница для сопоставления (например, списки). Следующее работает для меня. И чтобы подтвердить @Aleksei Matiushkin ниже, вышесказанное не будет работать и действительно не работает в схеме!

  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

Я очень рад, что копался в этом, когда я узнал кое-что о двух языках!

Ответы [ 2 ]

2 голосов
/ 10 февраля 2020

На самом деле вы можете сделать вид рекурсии, но смысл в том, чтобы подумать о том, что вы делаете, чтобы избежать вызова макроса внутри quote do [....] end. Ваш пример с IO.puts будет выглядеть примерно так:

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

вы заметите две вещи: я использую рекурсию в макросе, но вне кавычек, используя приватную функцию quote_value/1, и эта функция имеет logi c, который «останавливается» после того, как находит значение ниже 1. Все «кавычки» помещаются в список, и хитрость заключается в том, чтобы поместить этот список в кортеж {:__block__, [], put_quote_list_here}

Теперь обратите внимание, что этот макрос не будет компилироваться, если параметр списка test_re c не известен заранее (во время компиляции) , поэтому вам нужно вызвать макрос test_rec(["a", "b", 0, 100, 200]), чтобы компилятор знал размер и элементы этого списка.

Кстати, я использовал рекурсию, оптимизированную для тела, но вы можете легко добавить аккумулятор и преобразовать ее в рекурсию, оптимизированную для хвоста.

2 голосов
/ 10 февраля 2020

TL; DR: это невозможно.


Макросы в - это не то, что вы ожидаете от них. Когда компилятор видит макрос, он вызывает его во время компиляции и вводит AST , который он возвратил в том месте, где он был вызван. Тем не менее, рекурсивные макросы всегда приводят к бесконечному l oop на этапе компиляции.

Поместите IO.puts("something") перед инструкцией quote do, и вы увидите, что она будет напечатана бесконечно, один раз за последующий вызов, чтобы развернуть макрос.

Вы можете использовать @compile {:inline, test_rec: 1} для достижения желаемого поведения. Чтобы лучше понять макросы, вам, вероятно, следует прочитать раздел Macros в Руководстве по эликсиру в целом, и этот отрывок в частности:

Макросы сложнее написать, чем обычные функции Elixir, и считается плохим стилем использовать их, когда они не нужны. Так что пишите макросы ответственно.

...