我已经重新开始学习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
或看不到它们。我显然错过了某个地方的函数定义中非常基本的东西。我错过了什么?
答案 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
第二个命名函数不能直接访问周围的范围。
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可用/编译器函数已知的变量)在编译阶段被分配为局部变量,并被立即丢弃,因为没有人使用它们。
内部运行时函数中有可用的东西:
之所以可以使用它们,是因为编译器在看到模块属性和/或宏时会将生成的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
因此,您可能已经通过引入一个繁琐的宏,注入对实函数的调用来修复了代码,但我将其排除在此答案范围之外。有一种更简单的方法可以完成一项任务。
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
希望这会有所帮助。