我最近开始学习Elixir . 来自面向对象编程背景我无法理解Elixir函数 .
我正在关注Dave Thomas的书“Programming Elixir”> = 1.6,但我不太明白函数是如何工作的 .
在书中,他有以下例子:
handle_open = fn
{:ok, file} -> "Read data: #{IO.read(file, :line)}"
{_, error} -> "Error: #{:file.format_error(error)}"
end
handle_open.(File.open("code/intro/hello.exs")) # this file exists
-> "Read data: IO.puts \"Hello, World!\"\n"
handle_open.(File.open("nonexistent")) # this one doesn't
-> Error: no such file or directory"
我不明白参数是如何工作的 . 隐藏的if,else语句是否隐藏在某处?
2 回答
这里有几件事情,我会尽力覆盖所有这些事情 . 首先,这里使用了两种不同的功能 . 一个是命名函数(
File.open
),另一个是您创建的匿名函数,分配给变量handle_open
. 有一点difference in the way both are called .当你在
handle_open
函数中调用File.open
函数时,它基本上意味着你在其结果上调用handle_open
. 但是File.open/2函数本身可以返回两个值:{:ok, file}
如果文件存在{:error, reason}
如果它没有't (or if there' s另一个错误)handle_open
函数使用pattern matching和multiple function clauses来检查响应是什么并返回相应的消息 . 如果给定值"matches a specified pattern"它执行该语句,否则它将检查下一个模式 . 虽然在某种意义上,它类似于if-else
语句,但更好的类比是case关键字:在elixir中,您可以使用多个子句定义函数,而elixir使用所谓的 pattern matching 来确定要执行的子句 . 正如您所怀疑的那样,当您定义多个函数子句时,您可以有效地创建隐式_54668,例如 if 函数调用中指定的函数参数匹配第一个函数子句的参数, then 执行第一个函数子句, else if 函数调用中指定的函数参数匹配第二个函数子句的参数,然后执行第二个函数子句等 . 这里是一个例子:
my.exs:
(我通常不会在函数定义的多个子句之间留一个空行来表示它们都是相同的函数 . )
在iex中:
当你调用
calc(5, 1)
elixir转到calc()
的定义时,elixir以第一个函数子句开始,并尝试将args5, 1
与参数x, 1
匹配 . 因为参数x
是一个变量,它可以匹配任何内容,而参数1
只能匹配1
. 因此,匹配并且x被赋值为5
. 在其他语言中,您不能将1
,"hello"
,%{a: 1}
或:error
等值指定为函数定义中的函数参数 - 而是所有函数参数都必须是变量 .类似地,当你调用
calc(5, 2)
时,elixir转到calc()
的定义,并且elixir以第一个函数子句开始,并尝试将函数调用中的参数5, 2
与参数x, 1
匹配 .x
参数可以匹配任何内容,但1
参数与2
参数不匹配,因此elixir转到下一个函数子句并尝试将参数5, 2
与参数x, 2
匹配 . 这会产生一个匹配,x被赋值为5 .Elixir有一些非常有趣的匹配规则 . 以下是您将在某些时候遇到的一些规则,这些规则很难解读:
def func(%{] = x)
将匹配任何作为 Map 的参数 - 而不仅仅是空 Map - 并且 Map 将被分配给变量x
. 结构也是如此,例如:def func(%Dog{} = x)
将匹配任何Dog结构 .def func("hello " <> subject)
将匹配以"hello "
开头的任何字符串参数,并且字符串的其余部分将分配给subject
变量 .Elixir还允许您定义 anonymous functions with multiple clauses . 如果您将
test()
更改为:然后在iex中你会看到:
匹配与命名函数一样工作:elixir按顺序查看函数子句,并尝试将函数调用中的参数与函数子句中的参数进行匹配 . 请注意,在代码中定义函数子句的顺序可能很重要 . 定义一个永远不会执行的函数子句很容易,例如:
因为第一个函数子句中的参数x将匹配任何参数,所以第二个函数子句永远不会执行 . 幸运的是,如果你这样做,长生不老药会警告你 .
并且,因为elixir是一种函数式语言,所以不显示递归示例是不容错过的 . 将以下
count()
定义添加到My模块:然后在iex中:
当代码调用
count(n-1)
时,elixir转到count()
的第一个函数子句,然后尝试匹配参数n-1
的值与函数子句中的参数0
匹配 . 如果没有匹配,elixir会尝试第二个函数子句 . 结果,第二个函数子句保持匹配函数调用count(n-1)
直到全部输出数字包括1,因此n-1
在函数调用中等于0,导致函数调用count(0)
,它最终匹配第一个函数子句,并且第一个函数子句不输出任何内容,也不调用自身,所以递归结束 .在灵药中学习的关键是:
模式匹配
递归
产生其他进程
祝好运!