【问题标题】:Could someone explain these Haskell functions to me?有人可以向我解释这些 Haskell 函数吗?
【发布时间】:2010-10-28 19:47:59
【问题描述】:

我过去曾涉足 Haskell,最近又认真研究了它,我正在阅读真实世界的 Haskell。他们所展示的一些例子,我还没有理解。这样的:

myLength []     = 0
myLength (x:xs) = 1 + myLength (xs)

我不明白这是如何工作的,真正添加的 1 是什么?递归如何返回可以添加的东西?没看懂。

我们有这个:

splitLines [] = []
splitLines cs =
       let (pre, suf) = break isLineTerminator cs
       in  pre : case suf of 
                   ('\r':'\n':rest) -> splitLines rest
                   ('\r':rest)      -> splitLines rest
                   ('\n':rest)      -> splitLines rest
                   _                -> []

isLineTerminator c = c == '\r' || c == '\n'

这是如何工作的,pre 真正被附加的是什么?我看不出 case 表达式的结果是如何可以连接 pre 的。也许我只是需要有人详细解释这些功能的评估。我一定错过了一些非常重要的东西。

提前致谢!

编辑:我知道,这是复制粘贴失败。对不起。

编辑 2:我的困惑似乎在于这些函数实际上是什么/返回/我现在已经全部解决了。谢谢各位大神解答,终于成功了!我很感激!

【问题讨论】:

    标签: recursion functional-programming lazy-evaluation haskell


    【解决方案1】:

    我认为myLength的定义漏掉了列表为空的情况:

    myLength [] = 0
    myLength (x:xs) = 1 + myLength (xs)
    

    使用此定义,空列表的 myLength 为 0。(x:xs) 模式将列表解压缩为第一个项目 a,以及包含其余项目的列表 xs。如果列表有一项,则xs 是一个空列表,因此结果为 1 + 0。以此类推。

    当您首先查看基本情况时,递归最容易理解,然后查看每个级别的递归如何建立在结果之上。 (基本情况是函数不调用自身的情况。如果递归函数没有基本情况,则输出将是无限的。)

    在第二个示例中,基本案例(案例陈述中的最后一个案例)也是一个空列表。因此 pre 将始终附加到一个列表中,这将产生一个新的更长的列表。

    【讨论】:

      【解决方案2】:

      首先第一个例子应该是这样的(编辑:看起来你现在已经修好了):

      myLength []     = 0
      myLength (x:xs) = 1 + myLength (xs)
      

      它的工作原理是这样的:假设我给它一个包含三个项目的列表,它返回一加尾巴的长度(这是一加尾巴的长度(这是一加尾巴的长度,(此时为[]),即1),即w),即3(最终答案)。也许嵌套括号会帮助您理解它。 ;-)

      【讨论】:

        【解决方案3】:

        至于第一种,它是一种非常基本的递归方式。但是,它似乎缺少了一部分:

        myLength [] = 0
        

        它的工作原理是每次从列表中缩小一个元素并将一个添加到结果中。为了形象化,考虑调用

        myLength [1,2,3]
        

        这将评估为:

        1 + myLength [2,3]
        1 + 1 + myLength [3]
        1 + 1 + 1 + myLength []
        1 + 1 + 1 + 0
        

        这是 3。

        至于第二个,嗯,你已经将下一个换行符处的字符串分成两部分:pre 和 suf。现在,suf 将以 \n、\r 或 \r\n 开头。我们想删除这些。所以我们使用模式匹配。看看 rest 变量如何本质上是 suf 变量减去初始换行符。

        所以我们有pre,它是第一行,和rest,它是文本的其余部分。因此,为了继续将 rest 拆分为行,我们递归地调用 splitLines 并将结果连接到 pre。

        为了形象化,假设你有字符串“foo\nbar\r\nbaz”。

        所以,调用的时候,结果会是:

        [ pre => foo, suf => \nbar\r\nbaz, rest => bar\r\n\baz ]
        foo : splitLines bar\r\nbaz
        

        然后再次调用splitLines,结果展开为:

        [ pre => bar, suf => \r\nbaz, rest = baz ]
        foo : bar : splitLines baz
        

        然后再一次:

        [ pre => baz, suf => [], rest = [] ]
        foo : bar : baz
        

        这是最终的结果。

        【讨论】:

        • 我了解它是如何工作的,但现在我不知道为什么会这样。结果要么是另一个递归调用,要么是一个空列表。本质上,看起来 pre 每次重复都连接到 pre,但 case 表达式没有返回 pre。令人困惑。
        • 实际上在再次阅读之后,看起来 pre 正在连接到 splitLines 的参数,而不是结果。我只是不确定这是如何返回的,而不是最终返回 [] 的递归调用。我想我不像我想象的那样理解递归:\
        • 这发生在 "in pre : case suf of" 行中,由 : 运算符。
        • 以 Haskell 问题得到彻底彻底回答的速度,我认为您无需道歉。你清楚地考虑了一段时间,只是漏掉了一块。要求另一双眼睛指出你出轨的地方根本不是一件坏事。我发现对像这样的 Haskell 混淆有帮助的一种技术是非常仔细地查看我没有得到的任何函数或表达式的类型签名。它在 Haskell 中比在我用过的任何其他语言中都更有帮助。
        • 哦,你可以使用:t <expression>向ghci询问表达式的类型
        【解决方案4】:

        看看函数的类型签名是什么很有启发性。他们可能是:

        myLength :: [a] -> Int
        

        myLength 中,将 1 添加到对 myLength 的递归调用的结果中,这是一个 Int,这反过来会导致一个 Int

        splitLines :: [Char] -> [[Char]]
        

        splitLines 中,pre[Char])被添加到 case 语句的结果之前,从查看案例来看,它要么是递归调用 splitLines 的结果,要么是[[Char]];或一个空列表。在这两种情况下,前置 pre[Char])将依次生成 [[Char]]

        【讨论】:

        • 好的,我了解 myLength 示例。但是在书中它说 : (pre :) 的第二个参数是 case 表达式的结果,但是 case 表达式的结果要么是一个空列表(我得到)要么是对 splitLines 的递归调用,如何预先添加到任何东西?我正在努力解决这个问题。对不起,我是个白痴。 :\
        • 更多,我想我要问的是,如何递归调用 splitLines,导致 [[Char]]?
        • 好吧,你知道 (:) 是 "cons" 运算符,对吧;它接受一个元素和一个列表,并返回一个列表,该元素附加到另一个列表中。所以 (:) 的类型为“a -> [a] -> [a]”。所以由于 pre 的类型是 [Char],所以 splitLines 的结果是类型 [[Char]]。
        【解决方案5】:

        Re: myLength (x:xs) = 1 + myLength (xs) -- 这是myLength 定义的“一半”,它表示,通过模式匹配,if 参数有一个头和一个尾,那么结果比尾部的递归尾部调用多一个——当参数不能匹配x:xs,即当参数为空列表时,需要另一半说结果为0。

        在第二种情况下,通过case 使不同模式匹配的可能性更加明显。

        顺便说一句,懒惰不是这里的关键问题——ML,具有急切的评估但与 Haskell 非常相似的模式匹配,将非常相似地工作。看起来模式匹配是你真正需要复习的。

        【讨论】:

        • 感谢您的回答。我已经打包了模式匹配,并且在递归方面做得很好。我的错误在于这些函数实际上是/returning/。
        猜你喜欢
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-20
        • 1970-01-01
        • 1970-01-01
        • 2022-12-16
        相关资源
        最近更新 更多