发生了什么?

我找到了一个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,我无法推荐它。


追加headtail的时候head是偶数,则调用even tail一次。 因为我们追加headtail ,那不是正好被卡在一遍又一遍的增加头部的循环?

关闭但不完全。 它是通过递归even tail返回的列表的head 。 每次递归时,这个模式匹配表达式中的tail值都有一个较少的项。

另外,最后一行_::tail我假设的意思是“什么都不做,再次递归”,但是我在F#中查找了operator _ ,并且它说这是一个通配符模式。 这实际上是否意味着如果我的前两场比赛中没有任何一场比赛报道,请做这件事?

_可以是通配符模式,但是在这种模式匹配的例子中,它仅仅表明当我们解构列表时我们不关心头部值; 我们只关心tail 。 这个子句(因为它是在测试均匀性的子句之后出现)导致非偶数头值被丢弃。


您正在处理不可变数据类型(不可变列表)。 这意味着列表不会更改,每个操作都会创建并返回一个新列表。

模式匹配是将数据解构为多个部分的一种方式。 举个例子。 如果你有清单[1;2;3;4]并且你写了。

let list = [1;2;3;4]
let (head :: tail) = list

那么head是值1tail是列表[2;3;4]list仍然是[1;2;3;4]

让我们一步一步地通读您的示例。

even [1;2;3;4]被调用。 然后有三种模式匹配的情况。 第一个检查[1;2;3;4]是否为空列表。 它不是那么检查下一个。 head :: tail将列表提取为如上所示的两部分。 因此, head1tail代表[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]被调用。

然后再检查。

  • 是[3; 4]是一个空列表。 不。
  • head价值3由2整除都能跟得上。
  • 提取3_[4]taileven [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?

    下一篇: Tail Recursive map f#