简体   繁体   English

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

[英]Why is this elixir function undefined

I have rebooted my learning of the Elixir language. 我已经重新开始学习Elixir语言。 I am trying to try a variation of the Fizzbuzz problem with the following code. 我正在尝试使用以下代码来解决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

When I uncomment the do3 = genFB(3, "Fizz") line, I get the following error: 当我取消注释do3 = genFB(3, "Fizz")行时,出现以下错误:

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

I do not understand how genFB/2 can be undefined or not seen by the compiler. 我不明白genFB/2如何无法被编译器定义或看不到。 I have obviously missed something very fundamental in the definition of functions somewhere. 我显然错过了某个地方的函数定义中非常基本的东西。 What have I missed? 我错过了什么?

I do not understand how genFB/2 can be undefined or not seen by the compiler. 我不明白genFB / 2如何无法被编译器定义或看不到。 I have obviously missed something very fundamental in the definition of functions somewhere. 我显然错过了某个地方的函数定义中非常基本的东西。 What have I missed? 我错过了什么?

This example doesn't work either: 此示例也不起作用:

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

  greet()
end

In iex: 在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

Same error here: 同样的错误在这里:

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

  My.greet()
end

The reason is that defining a function doesn't introduce a name into the module scope, and you are calling the function at the module scope (for some unexplained reason). 原因是定义函数不会在模块作用域中引入名称,而您是在模块作用域中调用函数(出于某些无法解释的原因)。

Named Functions And Modules 命名功能和模块

...named functions have a couple of peculiarities. ...命名函数具有一些特殊性。

First, defining a named function does not introduce a new binding into the current scope: 首先,定义命名函数不会在当前范围内引入新的绑定:

 defmodule M do def foo, do: "hi" foo() # will cause CompileError: undefined function foo/0 end 

Second, named functions cannot directly access [the] surrounding scope. 第二,命名函数不能直接访问周围的范围。

Scoping Rules in Elixir Elixir中的作用域规则

That fact that def's don't create a name in the module scope should make you wonder how they can be called inside another function. def不会在模块作用域中创建名称的事实使您想知道如何在另一个函数中调用它们。

The answer to this lies in the following rule followed by Elixir when trying to resolve an identifier to its value: 答案在于以下规则,当尝试将标识符解析为其值时,Elixir遵循以下规则:

Any unbound identifier is treated as a local function call. 任何未绑定的标识符都被视为本地函数调用。

Huh? 咦? Translation: you can't call defs in the module scope--that's just the way it is! 翻译:您不能在模块范围内调用defs -就是这样!

I have rebooted my learning of the Elixir language. 我已经重新开始学习Elixir语言。

You can execute statements in an .exs file like this: 您可以在.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)> 

But if greet/0 is private ( defp ), you can't even do that. 但是,如果greet/0是私有( defp ),则您甚至无法做到这一点。

(Why do you name modules FizzBuzz.Fb4a which makes it as irritating as possible to type? What's the matter with the name F4 ?) (为什么为模块FizzBuzz.Fb4a命名,这使得键入时尽可能FizzBuzz.Fb4a ?名称为F4怎么了?)

Edit: it looks to me like defmodule creates the following scopes: 编辑:在我看来像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    -|-----|---------------+
|    +-------------+     |
|                        |
+------------------------+

You can see that the module scope is not accessible by inner scopes here: 您可以在此处看到内部作用域无法访问模块作用域:

defmodule My do
  x = 10

  def greet do
    IO.puts(x) 

  end

end

My.greet()

In iex: 在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)

The last part is an error saying x is undefined. 最后一部分是一个错误,指出x未定义。 But, there is a way to access the values in the module scope: 但是,有一种方法可以访问模块作用域中的值:

defmodule My do
  x = 10

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

end

My.greet()

In iex: 在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)> 

However, unquote() is a macro so I think that result is just a compiler trick, which doesn't have anything to do with runtime, ie you aren't looking up values in an outer scope, the compiler just inlined the values in your code at compile time. 但是, unquote()是一个宏,因此我认为结果只是一个编译器技巧,与运行时没有任何关系,即您不是在外部范围中查找值,编译器只是将值内联您的代码在编译时。

Compilation stages 编译阶段

I do not understand how genFB/2 can be undefined or not seen by the compiler. 我不明白genFB/2如何无法被编译器定义或看不到。

The main thing one should clearly understand about Elixir : it is, unlike many other languages, uses the same syntax for “metaprogramming” and the code itself. 关于Elixir,应该明确了解的主要内容:与许多其他语言不同,它对“元编程”和代码本身使用相同的语法。 Being a compiled language that runs inside a VM, Elixir is unable to execute any arbitrary code . 作为一种在VM内运行的编译语言, Elixir无法执行任何任意代码 The code must be compiled upfront. 该代码必须预先编译。

Compilation basically means converting the code into AST and then into BEAM. 编译基本上意味着将代码转换为AST ,然后转换为BEAM。

The code that is found in the topmost scope (and inside defmodule macro) is being executed on the compilation stage. 在编译范围内正在执行最顶层作用域(以及defmodule宏内)找到的代码。 It is not included into the resulting BEAM. 它不包含在生成的BEAM中。 Consider the following example: 考虑以下示例:

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

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

Test.yo

If you'll try to compile this, you'll see "🗜️ Compilation stage!" 如果您尝试对此进行编译,则会看到"🗜️ Compilation stage!" printed during compilation only. 在编译期间打印。 There is no way to refer to this code from the resulting BEAM, because it's simply discarded after execution during compilation. 无法从生成的BEAM中引用此代码,因为在编译过程中执行后将其简单丢弃。

OTOH, to get the "⚡ Runtime!" OTOH,以获取"⚡ Runtime!" string printed, you need to explicitly run Test.yo in runtime. 字符串打印后,您需要在运行时显式运行Test.yo

That said, your doN variables (even if they were referring valid aka available / known to the compiler functions) are assigned as local variables during the compilation stage and immediately discarded because nobody uses them. 就是说,您的doN变量(即使它们引用的是有效的aka可用/编译器函数已知的变量)在编译阶段被分配为局部变量,由于没有人使用它们而被立即丢弃

Workaround 1 解决方法1

There are things that are available inside runtime functions: 运行时函数内部有一些可用的东西:

Module attributes 模块属性

They are available because the compiler, when it sees a module attribute and/or macro, injects the resulting AST inplace, without touching it. 它们是可用的,因为当编译器看到模块属性和/或宏时, 它会将生成的AST注入到位而不接触它。 Consider the following example: 考虑以下示例:

defmodule Test do
  @mod_attr &IO.puts/1

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

Test.yo

Here we declared the module attribute, referencing function IO.puts/1 and called it from runtime. 在这里,我们声明了模块属性,引用了函数IO.puts/1 从运行时对其进行了调用。

The function we reference must be compiled at the moment it gets referenced . 我们引用的函数必须在被引用时编译

Macros

Consider the following example. 考虑以下示例。

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

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

Wait, what? 等一下 TheIt was printed during the compilation stage! TheIt是在编译阶段打印的! Yes, it was. 是的。 Macros inject the AST produced by their do: block, and therefore IO.puts(what) was executed during the compilation stage . 宏注入由其do:块产生的AST,因此IO.puts(what) 在编译阶段执行

To fix this behaviour one should quote the content of the macro, to inject it as is instead of executing it. 若要解决此问题,应quote宏的内容,按原样注入而不是执行它。

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

  def yo, do: puts("⚡ Runtime!")
end

Test.yo

So, you might have fixed your code by introducing a cumbersome macro, injecting the call to real function, but I'd leave this out of scope of this answer. 因此,您可能已经通过引入一个繁琐的宏,注入了对实函数的调用来修复了代码,但我将其排除在此答案范围之外。 There is way more easy way to accomplish a task. 有一种更简单的方法来完成一项任务。

Workaround 2 解决方法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

I have cleaned up the code a bit to use pattern matching instead of imperative if and cond clauses. 我对代码进行了一些整理,以使用模式匹配,而不是命令式的ifcond子句。

In the first place, you don't need to return a function. 首先,您不需要返回函数。 Elixir has a nifty feature allowing to capture functions with & . Elixir具有漂亮的功能,允许使用&捕获功能。 You might even assign the result of curried function to a variable and call it later, but it's not really needed here. 您甚至可以将curried函数的结果分配给变量,然后再调用它,但是这里并不需要它。

If you still want to assign the intermediate variables, make sure the module the function belongs to is already compiled . 如果仍要分配中间变量,请确保函数所属的模块已经编译

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

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM