我找到了一个f#递归函数的基本示例,它接受一个列表并返回一个只有偶数整数的列表。我在很大程度上理解这一点,但我对此感到困惑。
let numbers = [1..4]
let rec even ls =
match ls with
| [] -> []
|head :: tail when head % 2 = 0 -> head :: even tail
|_::tail -> even tail
匹配头部的线条让我感到困惑。这就是我读它的方式。当头部均匀时追加尾部,然后再次呼叫even tail
。因为我们一对一地追加尾巴,不会因为一次又一次地添加头而陷入困境?
此外,最后一行_::tail
我假设意味着什么都不做,再次递归",但是
我在f#中查找了运算符_
,它说它是一个通配符模式。这实际上意味着如果我的前两场比赛没有覆盖,那么这样做吗?
希望有人能澄清! f#看起来很有趣,但是从强制性的背景来看,它很难理解。
答案 0 :(得分:5)
这是模式匹配表达式。读取每一行的方法是:箭头->
左侧是如何检查数据的说明,箭头右侧是检查结果的说明。
将此应用于您的案例,请按照评论(为了清晰起见,我已插入一些换行符):
match ls with // Look at `ls`
| [] -> // when `ls` is an empty list,
[] // return an empty list
| head :: tail // when `ls` is a `head` attached to a `tail`,
when head % 2 = 0 -> // AND `head` is an even number,
head :: even tail // call `even tail`, attach `head` to it, and return that result
| _::tail -> // when `ls` is "something" attached to a `tail`,
even tail // call `even tail` and return that result
请注意,最后一种情况适用于第二种情况适用的所有情况。这种歧义通过案例的顺序得到解决:程序将依次根据每个案例尝试匹配(“查看”,“检查”)数据,并将执行匹配的第一个案例的代码。
您似乎缺少的另一个微妙点:不变性。该列表是不可变的。您无法“更新”(“更改”,“更改”)列表。如果你有一个列表,它将始终保持这种状态,编译器保证它。相反,数据通过程序演变的方式是基于旧数据创建新数据。特别是,如果您说a :: b
,则不会修改列表b
,而是创建一个新列表,其中a
为首,b
为尾。< / p>
这些是F#中非常基本的概念,而你错过它们这一事实告诉我你刚刚开始研究这种语言(也许还有一般的函数式编程)。如果这是真的,我建议先阅读一些介绍性材料,以熟悉基本概念。我最喜欢的是fsharpforfunandprofit.com,我不能推荐它。
答案 1 :(得分:4)
当
head
为偶数时,将tail
附加到head
,然后再次致电even tail
。因为我们将head
附加到tail
,所以不会只是在一次又一次地添加头部的循环中被抓住了吗?
关闭但不完全。它将 head
添加到递归even tail
返回的列表中。每次递归时,此模式匹配表达式中的tail
值都会少一个项目。
此外,我假设的最后一行
_::tail
意味着“什么都不做,再次递归”,但我在F#中查找了运算符_
,它说它是一个通配符模式。这实际上意味着如果我的前两场比赛没有覆盖,那么这样做吗?
_
可以是通配符模式,但在此模式匹配示例中,它只是表示我们在解构列表时不关心 head 值;我们只关心tail
。此子句(因为它位于测试均匀性的子句之后)会导致非偶数 head 值被丢弃。
答案 2 :(得分:1)
您正在处理不可变数据类型(不可变列表)。这意味着列表不会更改,每个操作都会创建并返回一个新列表。
模式匹配是一种将数据解构为多个部分的方法。举个例子。如果你有列表[1;2;3;4]
并且你写了。
let list = [1;2;3;4]
let (head :: tail) = list
然后head
是值1
。 tail
列表为[2;3;4]
,list
仍为[1;2;3;4]
。
让我们一步一步地完成您的示例。
调用 even [1;2;3;4]
。然后有三种模式匹配的情况。第一个检查[1;2;3;4]
是否为空列表。它并非如此,它会检查下一个。 head :: tail
将列表提取为两个部分,如上所述。因此head
为1
而tail
代表[2;3;4]
,但when head % 2 = 0
添加了条件。它会检查head
(当前1
)是否可以分为两个。它不是,所以它转到最后的模式匹配。
最后一个是_::tail
。首先,它与head::tail
完全相同。它提取第一个值1
并将其存储在变量_
中。使用_
作为变量名的原因是为了澄清从未使用过的第一个名称。您也可以将_
更改为head
,代码的工作方式相同。
最后一次模式匹配匹配,现在您1
存储了_
,[2;3;4]
存储了tail
。你要做的只是致电even tail
。或者在这种情况下,您调用even [2;3;4]
并返回此函数调用的结果作为结果。
调用even [2;3;4]
时,它会执行相同的操作。
检查它是否为空列表。不是。然后,它将2
和head
中的第一个值[3;4]
提取到tail
。它检查何时的状况。这一次是真的。现在它执行head :: even tail
或者,如果我们替换值2 :: even [3;4]
::
是列表的串联,但在我们可以连接列表之前,首先函数调用even [3;4]
需要返回一个列表。所以even [3;4]
被调用。
然后再次检查。
head
值3
是否可以分割2. Nope。3
提取到_
,将[4]
提取到tail
,然后致电even [4]
。 even [4]
然后做同样的事情:
[4]
是否为空列表。不。4
分配给head
的值是偶数吗?是的。所以调用了4 :: even []
。 even []
然后做同样的事情:
[]
是否为空列表。是的。所以返回空列表。 []
然后它倒退了。
-> means "returns"
even [] -> []
4 :: even [] -> [4]
even [3;4] -> [4]
2 :: even [3;4] -> [2;4]
even [2;3;4] -> [2;4]
even [1;2;3;4] -> [2;4]