发生了什么?
我找到了一个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#中查找了operator _
,并且它说这是一个通配符模式。 这实际上是否意味着如果我的前两场比赛中没有任何一场比赛报道,请做这件事?
希望有人能澄清! f#看起来很有趣,但是来自必要的背景很难理解。
这是一个模式匹配表达式。 阅读每一行的方法是:在箭头的左侧->
是如何检查数据的说明,箭头的右侧是检查结果的说明。
将其应用于您的案例,请遵循评论(为了清晰起见,我插入了一些换行符):
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
是head而b
是tail。
这些都是F#中非常基本的概念,而且你错过了它们的事实告诉我,你只是刚开始看语言(也许在一般的函数式编程中)。 如果这是真的,我建议先阅读一些介绍性材料,以熟悉基本概念。 我最喜欢的是fsharpforfunandprofit.com,我无法推荐它。
追加head
到tail
的时候head
是偶数,则调用even tail
一次。 因为我们追加head
到tail
,那不是正好被卡在一遍又一遍的增加头部的循环?
关闭但不完全。 它是通过递归even tail
返回的列表的head
。 每次递归时,这个模式匹配表达式中的tail
值都有一个较少的项。
另外,最后一行_::tail
我假设的意思是“什么都不做,再次递归”,但是我在F#中查找了operator _
,并且它说这是一个通配符模式。 这实际上是否意味着如果我的前两场比赛中没有任何一场比赛报道,请做这件事?
_
可以是通配符模式,但是在这种模式匹配的例子中,它仅仅表明当我们解构列表时我们不关心头部值; 我们只关心tail
。 这个子句(因为它是在测试均匀性的子句之后出现)导致非偶数头值被丢弃。
您正在处理不可变数据类型(不可变列表)。 这意味着列表不会更改,每个操作都会创建并返回一个新列表。
模式匹配是将数据解构为多个部分的一种方式。 举个例子。 如果你有清单[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]
并返回此函数调用的结果作为结果。
当even [2;3;4]
被调用时,它的确如上所述。
它检查它是否为空列表。 不是。 然后它提取head
的第一个值2
和[3;4]
到tail
。 它检查何时条件。 这一次是真的。 所以现在它执行head :: even tail
或者,如果我们将值替换为2 :: even [3;4]
::
是一个列表的连接,但在我们可以连接一个列表之前,首先函数调用even [3;4]
需要返回一个列表。 所以even [3;4]
被调用。
然后再检查。
head
价值3
由2整除都能跟得上。 3
到_
和[4]
到tail
, even [4]
调用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]
链接地址: http://www.djcxy.com/p/80561.html
上一篇: What's happening?