为什么这个长生不老药功能未定义

时间:2019-05-04 21:49:19

标签: elixir

我已经重新开始学习Elixir语言。我正在尝试使用以下代码尝试解决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 个答案:

答案 0 :(得分:2)

  

我不明白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
     

第二个命名函数不能直接访问周围的范围。

Scoping Rules in 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 :(得分:1)

编译阶段

  

我不明白编译器如何无法定义genFB/2或看不到它们。

关于 Elixir 的主要事情应该是清楚的:与许多其他语言不同,它对“元编程”和代码本身使用相同的语法。作为在VM内运行的一种编译语言, Elixir 无法执行任何任意的 code 必须先编译代码。

编译基本上意味着将代码转换为AST ,然后转换为BEAM。

在编译范围内正在执行在最高作用域(和defmodule宏内)中找到的代码。 它不包含在生成的BEAM中。请考虑以下示例:

defmodule Test do
  IO.puts "️ Compilation stage!"

  def yo, do: IO.puts "⚡ Runtime!"
end

Test.yo

如果您尝试对其进行编译,则只会看到"️ Compilation stage!"编译期间打印。无法从生成的BEAM中引用此代码,因为在编译过程中执行后便将其丢弃。

OTOH,要打印"⚡ Runtime!"字符串,需要在运行时显式运行Test.yo

也就是说,您的doN变量(即使它们引用了有效的aka可用/编译器函数已知的变量)在编译阶段被分配为局部变量,并被立即丢弃,因为没有人使用它们。

解决方法1

内部运行时函数中有可用的东西:

模块属性

之所以可以使用它们,是因为编译器在看到模块属性和/或宏时会将生成的AST注入到位,而不会触及它。请考虑以下示例:

defmodule Test do
  @mod_attr &IO.puts/1

  def yo, do: @mod_attr.("⚡ Runtime!")
end

Test.yo

这里我们声明了模块属性,引用了函数IO.puts/1 并从运行时调用了它们。

我们引用的函数必须在被引用时立即编译

请考虑以下示例。

defmodule Test do
  defmacrop puts(what), do: IO.puts(what)

  def yo, do: puts("️ Compilation time!")
end

等等,什么? The 是在编译阶段打印的!是的。宏会注入由其do:块产生的AST,因此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

我对代码进行了一些整理,以使用模式匹配,而不是强制性的ifcond子句。

首先,您不需要返回函数。 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

希望这会有所帮助。