在 elixir 中,您可以定义具有多个子句的函数,并且 elixir 使用所谓的 模式匹配 来确定要执行哪个子句。正如您所怀疑的,当您定义多个函数子句时,您有效地创建了一个隐式if-else if,例如如果函数调用中指定的函数参数与第一个函数子句的参数匹配,则执行第一个函数子句,else if 函数调用中指定的函数参数匹配第二个函数子句的参数,然后执行第二个函数子句,等等。下面是一个例子:
我的.exs:
defmodule My do
def calc(x, 1) do
x * 3
end
def calc(x, 2) do
x - 4
end
def test do
IO.puts calc(5, 1)
IO.puts calc(5, 2)
end
end
(我通常不会在函数定义的多个子句之间留空行来表示它们都是同一个函数。)
在 iex 中:
$ 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]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> My.test
15
1
:ok
iex(2)>
当您调用calc(5, 1) 时,elixir 转到calc() 的定义,并且elixir 从第一个函数子句开始并尝试将参数5, 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) 将匹配任何作为映射的参数——而不仅仅是一个空映射——并且映射将被分配给变量x。结构也是如此,例如def func(%Dog{} = x) 将匹配任何 Dog 结构。
def func("hello " <> subject) 将匹配任何以"hello " 开头的字符串参数,而字符串的其余部分将分配给subject 变量。
Elixir 还允许您定义 具有多个子句的匿名函数。如果您将test() 更改为:
def test do
func = fn
(:ok, x) -> IO.puts ":ok branch, x = #{x}"
(y, :error) -> IO.puts ":error branch, y = #{y}"
end
func.("hello", :error)
func.(:ok, 10)
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]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> My.test()
:error branch, y = hello
:ok branch, x = 10
:ok
iex(2)>
匹配的工作方式与命名函数类似:elixir 按顺序查看函数子句,并尝试将函数调用中的参数与函数子句中的参数进行匹配。请注意,您在代码中定义函数子句的顺序可能很重要。定义一个永远不会执行的函数子句很容易,例如:
func = fn
(x) -> ...
(:ok) -> ...
end
因为第一个函数子句中的参数 x 将匹配任何参数,所以第二个函数子句永远无法执行。幸运的是,如果你这样做,elixir 会警告你。
而且,因为 elixir 是一种函数式语言,所以不显示递归示例将是失职。在我的模块中添加count()的如下定义:
def count(0) do
:ok
end
def count(n) when n > 0 do
Process.sleep 1_000 #sleep for 1 second
IO.puts n
count(n-1)
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] Interactive Elixir (1.6.6) - press Ctrl+C to exit
(type h() ENTER for help)
iex(1)> My.count(10)
10
9
8
7
6
5
4
3
2
1
:ok
iex(2)>
当代码调用count(n-1) 时,elixir 会转到count() 的第一个函数子句,然后尝试匹配参数n-1 的值与函数子句中的参数0 的值。如果没有匹配,elixir 会尝试第二个函数子句。结果第二个函数子句一直匹配函数调用count(n-1),直到输出包括1在内的所有数字,于是函数调用中n-1等于0,导致函数调用count(0),最终匹配第一个函数子句,第一个函数子句不输出任何东西,也不调用自己,所以递归结束。
在 Elixir 中要学习的关键内容是:
- 模式匹配
- 递归
- 产生其他进程
祝你好运!