Этапы компиляции
Я не понимаю, как genFB/2
может быть неопределен или не виден компилятором.
Главное, что нужно четко понимать в Elixir : он, в отличие от многих других языков, использует тот же синтаксис для «метапрограммирования» и самого кода.Будучи скомпилированным языком, который работает внутри виртуальной машины, Elixir не может выполнить произвольный код . Код должен быть скомпилирован заранее.
Компиляция в основном означает преобразование кода в AST , а затем в BEAM.
Код, найденный всамая верхняя область (и внутри макроса defmodule
) выполняется на этапе компиляции. Он не включен в получившийся луч. Рассмотрим следующий пример:
defmodule Test do
IO.puts "?️ Compilation stage!"
def yo, do: IO.puts "⚡ Runtime!"
end
Test.yo
Если вы попытаетесь скомпилировать это, вы увидите "?️ Compilation stage!"
напечатанный во времятолько компиляция .Нет никакого способа обратиться к этому коду из полученного BEAM, потому что он просто отбрасывается после выполнения во время компиляции.
OTOH, чтобы получить напечатанную строку "⚡ Runtime!"
, вам нужно явно запустить Test.yo
во время выполнения.
Тем не менее, ваши doN
переменные (даже если они ссылались на действительные или доступные для функций компилятора) назначаются в качестве локальных переменных на этапе компиляции и немедленно отбрасываются , посколькуникто не использует их.
Обходной путь 1
Есть вещи, которые доступны внутри функции времени выполнения:
Атрибуты модуля
Они доступны, поскольку компилятор, когдаон видит атрибут модуля и / или макрос, вставляет полученный AST на место, не касаясь его. Рассмотрим следующий пример:
defmodule Test do
@mod_attr &IO.puts/1
def yo, do: @mod_attr.("⚡ Runtime!")
end
Test.yo
Здесь мы объявили атрибут модуля, refeфункция вызова IO.puts/1
и вызывали ее из среды выполнения.
Функция, на которую мы ссылаемся , должна быть скомпилирована в момент обращения к ней .
Макросы
Рассмотрим следующий пример.
defmodule Test do
defmacrop puts(what), do: IO.puts(what)
def yo, do: puts("?️ Compilation time!")
end
Подождите, что?Он был напечатан на этапе компиляции! Да, так и было.Макросы внедряют AST, созданный их блоком do:
, и поэтому IO.puts(what)
был выполнен на этапе компиляции .
. Чтобы исправить это поведение, нужно quote
содержимое макроса, чтобы внедрить его как вместо его выполнения.
defmodule Test do
defmacrop puts(what), do: quote(do: IO.puts(unquote(what)))
def yo, do: puts("⚡ Runtime!")
end
Test.yo
Итак, вы могли бы исправить свой код, введя громоздкий макрос, добавив вызовреальная функция, но я бы оставил это вне рамок этого ответа.Есть более простой способ выполнить задачу.
Обходной путь 2
defmodule FizzBuzz.Fb4a do
def upto(n) when n > 0, do: fizzbuzz(n)
defp toTuple(n), do: {n, ""}
defp toString({v, ""}), do: v
defp toString({_v, a}), do: a
defp genFB({v, a}, d, s) when rem(v, d) == 0, do: {v, a <> s}
defp genFB({v, a}, _d, _s), do: {v, a}
defp fizzbuzz(n) do
1..n
|> Enum.map(&toTuple/1)
|> Enum.map(&genFB(&1, 3, "Fizz"))
|> Enum.map(&genFB(&1, 5, "Bazz"))
|> Enum.map(&genFB(&1, 7, "Bang"))
|> Enum.map(&toString/1)
end
end
Я немного очистил код, чтобы использовать сопоставление с образцом вместо обязательных if
и cond
пункты.
Во-первых, вам не нужно возвращать функцию. Elixir имеет отличную функцию, позволяющую захватывать функции с помощью &
.Вы можете даже присвоить результат функции curried переменной и вызвать ее позже, но здесь это на самом деле не нужно.
Если вы все еще хотите присвоить промежуточные переменные, убедитесь, что модуль, которому принадлежит функция уже скомпилировано .
defmodule FizzBuzz.Fb4a do
defmodule Gen do
def genFB({v, a}, d, s) when rem(v, d) == 0, do: {v, a <> s}
def genFB({v, a}, _d, _s), do: {v, a}
end
def upto(n) when n > 0, do: fizzbuzz(n)
defp toTuple(n), do: {n, ""}
defp toString({v, ""}), do: v
defp toString({_v, a}), do: a
defmacrop do3, do: quote(do: &Gen.genFB(&1, 3, "Fizz"))
defmacrop do5, do: quote(do: &Gen.genFB(&1, 5, "Bazz"))
defmacrop do7, do: quote(do: &Gen.genFB(&1, 7, "Bang"))
defp fizzbuzz(n) do
1..n
|> Enum.map(&toTuple/1)
|> Enum.map(do3())
|> Enum.map(do5())
|> Enum.map(do7())
|> Enum.map(&toString/1)
end
end
Надеюсь, это поможет.