Recursion 谁能给我解释一下这些Haskell函数吗?

Recursion 谁能给我解释一下这些Haskell函数吗?,recursion,functional-programming,lazy-evaluation,haskell,Recursion,Functional Programming,Lazy Evaluation,Haskell,我过去曾涉猎过Haskell,最近又认真地重新涉猎,我正在阅读现实世界中的Haskell。他们所展示的一些例子,我还没有理解。在这一点上: myLength [] = 0 myLength (x:xs) = 1 + myLength (xs) splitLines [] = [] splitLines cs = let (pre, suf) = break isLineTerminator cs in pre : case suf of

我过去曾涉猎过Haskell,最近又认真地重新涉猎,我正在阅读现实世界中的Haskell。他们所展示的一些例子,我还没有理解。在这一点上:

myLength []     = 0
myLength (x:xs) = 1 + myLength (xs)
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'
我不明白这是怎么回事,1到底是怎么被添加的呢?递归如何返回可以添加到的内容?我不明白

这里我们有一个:

myLength []     = 0
myLength (x:xs) = 1 + myLength (xs)
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:似乎我对这些函数的实际用途感到困惑/返回/我现在已经全部解决了。谢谢大家的回答,终于点击了!我很感激

我认为
myLength
的定义忽略了列表为空的情况:

myLength [] = 0
myLength (x:xs) = 1 + myLength (xs)
根据此定义,空列表的
myLength
为0。
(x:xs)
模式将列表解压为第一项
a
,以及包含其余项的列表
xs
。如果列表有一项,那么
xs
是一个空列表,因此结果为1+0。等等

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

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

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

在第二种情况下,通过
case
使不同模式匹配的可能性更大一些


顺便说一句,懒惰在这里不是一个关键问题——ML与Haskell非常相似,具有热切的评估,但模式匹配。看起来模式匹配是您真正需要复习的内容。

首先,第一个示例应该是这样的(编辑:看起来您现在已经修复了它):


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

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

myLength :: [a] -> Int
myLength
中,1被添加到对
myLength
的递归调用的结果中,这是一个
Int
,这反过来会导致一个
Int

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

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

对于第一种情况,这是一种非常基本的递归方法。然而,它似乎缺少一个部分:

myLength [] = 0
它的工作原理是一次从列表中缩小一个元素,然后将一个元素添加到结果中。为了可视化,考虑调用< /P>
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

这是最终结果。

好的,我理解myLength示例。但是在书中,它说:(pre:)的第二个参数是case表达式的结果,但是case表达式的结果要么是一个空列表(我得到的),要么是对splitLines的递归调用,pre是如何添加到任何东西的?我正试着在脑海里解决这个问题。对不起,我是个白痴\另外,我想我要问的是,对splitLines的递归调用如何产生[[Char]]?你知道(:)是“cons”操作符,对吧;它接受一个元素和一个列表,并返回一个列表,其中该元素位于另一个列表的前面。所以(:)的类型是“a->[a]->[a]”。因为pre有[Char]类型,所以splitLines的结果有[Char]类型。我理解这是如何工作的,但现在我不明白为什么会这样。结果要么是另一个递归调用,要么是一个空列表。从本质上看,每次重复都会将pre连接到pre,但case表达式不会返回pre。令人困惑。事实上,在再次阅读之后,pre似乎连接到splitLines的参数,而不是结果。我只是不知道这是怎么回事