Почему эта функция эликсира не определена - PullRequest
0 голосов
/ 05 мая 2019

Я перезагрузил свое изучение языка эликсира. Я пытаюсь попробовать вариант проблемы Fizzbuzz с помощью следующего кода.

defmodule FizzBuzz.Fb4a do

  def upto(n) when n > 0 do
    fizzbuzz(n)
  end

  defp toTuple(n), do: {n, ""}

  defp toString({v,a}) do
    if String.length(a) == 0 do v else a end
  end

  defp genFB(d, s) do
    fn ({v, a}) ->
      cond do
        rem(v, d) == 0 -> {v, a+s}
        true           -> {v, a}
      end
    end
  end

  # do3 = genFB(3, "Fizz")
  # do5 = genFB(5, "Buzz")
  # do7 = genFB(7, "Bang")

  defp fizzbuzz(n) do
    1..n
    |> Enum.map(&toTuple/1)
    # |> Enum.map(&do3/1)
    # |> Enum.map(&do5/1)
    # |> Enum.map(&do7/1)
    |> Enum.map(&toString/1)
  end

end

Когда я раскомментирую строку do3 = genFB(3, "Fizz"), я получаю следующую ошибку:

** (CompileError) lib/fib4a.ex:22: undefined function genFB/2

Я не понимаю, как genFB/2 может быть неопределен или не виден компилятором. Я явно упустил что-то очень фундаментальное в определении функций где-то. Что я пропустил?

Ответы [ 2 ]

2 голосов
/ 05 мая 2019

Я не понимаю, как genFB / 2 может быть не определен или не виден компилятором.Я явно упустил что-то очень фундаментальное в определении функций где-то.Что я пропустил?

Этот пример также не работает:

defmodule My do
  def greet do
    IO.puts "hello"
  end

  greet()
end

В iex:

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

** (CompileError) my.exs:6: undefined function greet/0

Та же ошибка здесь:

defmodule My do
  def greet do
    IO.puts "hello"
  end

  My.greet()
end

Причина заключается в том, что определение функции не вводит имя в область видимости модуля, и вы вызываете функцию в области видимости модуля (по какой-то необъяснимой причине).

Именованные функции и модули

... именованные функции имеют несколько особенностей.

Во-первых, определение именованной функции не вводит новую привязку в текущую область:

defmodule M do
  def foo, do: "hi"

  foo()  # will cause CompileError: undefined function foo/0
end

Во-вторых, именованные функции не могут получить прямой доступ к [] окружающей области.

Правила определения объема в Elixir

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

Ответ на этот вопрос заключается в следующем правиле, за которым следует Elixir при попытке разрешитьидентификатор его значения:

Любой несвязанныйидентификатор обрабатывается как локальный вызов функции.

А?Перевод: вы не можете вызывать defs в области видимости модуля - это просто так!

Я перезапустил изучение языка Elixir.

Вы можете выполнять операторы в файле .exs следующим образом:

defmodule My do
  def greet do
    IO.puts "hello"
  end

end

My.greet()  #<====  This will execute

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

hello   #<=== Output
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

Но если greet/0 является личным (defp), вы даже не сможете этого сделать.

(Почему вы называете модули FizzBuzz.Fb4a, что делает ввод текста настолько раздражающим, насколько это возможно)? Что случилось с именем F4?)

Редактировать: мне кажется, что defmodule создает следующие области:

defmodule My do
  x = 100

  def greet do
    x = 1
  end

  def go do
    x = 3
  end
end

  ||
  VV

+----MyModuleScope-------+ 
|                        |
|    x = 100             |  
|                        |
|    +--greetScope-+     |      +--Invisible Scope--+
|    |   x = 1    -|-----|----->|       greet       |
|    +-------------+     |      |       go          |
|                        |      +-------------------+
|    +--goScope----+     |               ^    
|    |   x = 3    -|-----|---------------+
|    +-------------+     |
|                        |
+------------------------+

Вы можетеобратите внимание, что область видимости модуля недоступна для внутренних областей:

defmodule My do
  x = 10

  def greet do
    IO.puts(x) 

  end

end

My.greet()

В iex:

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

warning: variable "x" is unused
  my.exs:2

warning: variable "x" does not exist and is being expanded to "x()", please use parentheses to remove the ambiguity or change the variable name
  my.exs:5

** (CompileError) my.exs:5: undefined function x/0
    (stdlib) lists.erl:1338: :lists.foreach/2
    my.exs:1: (file)

Последняя часть - ошибка, в которой говорится, что x не определен.Но есть способ получить доступ к значениям в области видимости модуля:

defmodule My do
  x = 10

  def greet do
    x = 1
    IO.puts(unquote(x)) 
    x
  end

end

My.greet()

В iex:

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

10
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

Однако unquote() - это макрос, поэтому я думаю, что результатпросто трюк с компилятором, который не имеет ничего общего со временем выполнения, то есть вы не ищите значения во внешней области видимости, компилятор просто вставил значения в ваш код во время компиляции.

1 голос
/ 06 мая 2019

Этапы компиляции

Я не понимаю, как 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

Надеюсь, это поможет.

...