有人可以向我解释这些Haskell功能吗?

时间:2022-02-12 08:19:49

I've dabbled with Haskell in the past, and recently got back into it seriously, and I'm reading real world haskell. Some of the examples they've shone, I've yet to understand. Such at this one:

我过去曾经涉足过Haskell,最近又认真地回到了它,我正在阅读现实世界的haskell。他们已经发光的一些例子,我还没有理解。在这一个:

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

I don't see how this works, what is 1 really being added too? How is the recursion returning something that can be added to? I don't get it.

我不知道这是如何工作的,真正添加的是什么?递归如何返回可以添加的内容?我不明白。

And here we have this one:

在这里我们有这个:

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'

How does this work, what is pre really being attached too? I don't see how the the result of the case expression is something that pre can be concatenated to. Maybe I just need someone to explain the evaluation of these functions in details. I must be missing something very vital.

这是如何工作的,什么是真正的附加呢?我没有看到case表达式的结果是如何预先连接到的。也许我只需要有人详细解释这些功能的评估。我必须遗漏一些非常重要的东西。

Thanks in advance!

提前致谢!

EDIT: I know, it was a copy-paste fail. Sorry.

编辑:我知道,这是一个复制粘贴失败。抱歉。

EDIT 2: It seems my confusion was with what these functions were actually /returning/ I have it all worked out now. Thanks for the answers guys, it finally clicked! I appreciate it!

编辑2:似乎我的困惑在于这些功能实际/返回/我现在已经解决了这些问题。谢谢你们的答案,终于点击了!我很感激!

5 个解决方案

#1


As for the first one, it's a very basic way of recursion. However, it seems to be missing a part:

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

myLength [] = 0

It works by scaling off one element at the time from the list and adding one to the result. To visualise, consider the call

它的工作原理是从列表中缩小一个元素并将一个元素添加到结果中。要想象,请考虑通话

myLength [1,2,3]

which will evaluate to:

将评估为:

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

which is 3.

这是3。

As for the second one, well, you have already split the string at the next line break into two parts: pre and suf. Now, suf will start with either a \n, or a \r, or a \r\n. We want to remove these. So we use pattern matching. See how the rest variable is essentially the suf variable minus the initial line break character(s).

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

So we have pre, which is the first line, and rest, which is the rest of the text. So in order to continue splitting rest into lines we call splitLines on it recursively and concatenate the result to pre.

所以我们有pre,这是第一行,rest是文本的其余部分。因此,为了继续将休息分成行,我们以递归方式调用splitLines并将结果连接到pre。

To visualize, say you have the string "foo\nbar\r\nbaz".

要想象,请说你有字符串“foo \ nbar \ r \ n \ nbaz”。

So, when calling, the result will be:

所以,在调用时,结果将是:

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

then splitLines is called again, and the result is expanded into:

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

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

then once again:

再一次:

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

which is the final result.

这是最后的结果。

#2


I think the definition of myLength misses the case where the list is empty:

我认为myLength的定义错过了列表为空的情况:

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

With this definition, the myLength of an empty list is 0. The (x:xs) patten unpacks a list into the first item, a, and a list with the rest of the items, xs. If the list has one item, then xs is an empty list, so the result is 1 + 0. And so on.

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

Recursion is easiest to understand when you look at the base case first, and then see how each level of recursion builds on the result. (The base case is the case where the function does not call itself. If a recursive function doesn't have a base case, the output will be infinite.)

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

In the second example, the base case (the last case in the case-statment) is also an empty list. So pre will always be appended to a list, which will yield a new, longer, list.

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

#3


Re: myLength (x:xs) = 1 + myLength (xs) -- this is "half" of the definition of myLength, it says, by pattern match, that if the argument has a head and a tail, then the result is one more than the recursive tail call on the tail -- there needs to be another half to say that the result is 0 when the argument cannot match x:xs, i.e., when the argument is an empty list.

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

In the second case, the possibility of different patterns matching is just made a bit more epxlicit via case.

在第二种情况下,不同模式匹配的可能性只是通过案例更加明确。

BTW, laziness is not a key issue here -- ML, with eager evaluation but pattern matching much like Haskell, would work very similarly. Looks like pattern matching is what you really need to brush up about.

顺便说一句,懒惰不是一个关键的问题 - ML,热切的评估,但模式匹配很像Haskell,将工作非常相似。看起来模式匹配是你真正需要了解的。

#4


First of all the first example should be like this (edit: it looks like you fixed it now):

首先,第一个例子应该是这样的(编辑:它看起来像你现在修复它):

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

It works like this: say I give it a list with three items, it returns one plus the length of the tail (which is one plus the length of the tail (which is one plus the length of the tail, (which is [] at this point), which is 1), which is w), which is 3 (the final answer). Maybe nested parenthesis will help you understand it. ;-)

它的工作方式如下:说我给它一个包含三个项目的列表,它返回一个加上尾部的长度(这是一个加上尾部的长度(这是一个加上尾部的长度,(是[]此时),即1),即w),即3(最终答案)。也许嵌套的括号将帮助您理解它。 ;-)

#5


It is instructive to look at what the type signatures of the functions would be. They could be:

查看函数的类型签名是有益的。他们可能是:

myLength :: [a] -> Int

In myLength, 1 is being added to the result of the recursive call to myLength, which is an Int, which in turn results in an Int.

在myLength中,1被添加到myLength的递归调用的结果中,这是一个Int,它反过来导致一个Int。

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

In splitLines, pre (a [Char]) is being prepended to the result of the case statement, which, from looking at the cases, is either the result of a recursive call to splitLines, which is [[Char]]; or an empty list. In both cases, prepending pre (a [Char]) will result in a [[Char]] in turn.

在splitLines中,pre(a [Char])被置于case语句的结果之前,从查看案例中,它是对splitLines的递归调用的结果,即[[Char]];或一个空列表。在这两种情况下,预先加上pre(a [Char])将依次产生[[Char]]。

#1


As for the first one, it's a very basic way of recursion. However, it seems to be missing a part:

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

myLength [] = 0

It works by scaling off one element at the time from the list and adding one to the result. To visualise, consider the call

它的工作原理是从列表中缩小一个元素并将一个元素添加到结果中。要想象,请考虑通话

myLength [1,2,3]

which will evaluate to:

将评估为:

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

which is 3.

这是3。

As for the second one, well, you have already split the string at the next line break into two parts: pre and suf. Now, suf will start with either a \n, or a \r, or a \r\n. We want to remove these. So we use pattern matching. See how the rest variable is essentially the suf variable minus the initial line break character(s).

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

So we have pre, which is the first line, and rest, which is the rest of the text. So in order to continue splitting rest into lines we call splitLines on it recursively and concatenate the result to pre.

所以我们有pre,这是第一行,rest是文本的其余部分。因此,为了继续将休息分成行,我们以递归方式调用splitLines并将结果连接到pre。

To visualize, say you have the string "foo\nbar\r\nbaz".

要想象,请说你有字符串“foo \ nbar \ r \ n \ nbaz”。

So, when calling, the result will be:

所以,在调用时,结果将是:

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

then splitLines is called again, and the result is expanded into:

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

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

then once again:

再一次:

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

which is the final result.

这是最后的结果。

#2


I think the definition of myLength misses the case where the list is empty:

我认为myLength的定义错过了列表为空的情况:

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

With this definition, the myLength of an empty list is 0. The (x:xs) patten unpacks a list into the first item, a, and a list with the rest of the items, xs. If the list has one item, then xs is an empty list, so the result is 1 + 0. And so on.

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

Recursion is easiest to understand when you look at the base case first, and then see how each level of recursion builds on the result. (The base case is the case where the function does not call itself. If a recursive function doesn't have a base case, the output will be infinite.)

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

In the second example, the base case (the last case in the case-statment) is also an empty list. So pre will always be appended to a list, which will yield a new, longer, list.

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

#3


Re: myLength (x:xs) = 1 + myLength (xs) -- this is "half" of the definition of myLength, it says, by pattern match, that if the argument has a head and a tail, then the result is one more than the recursive tail call on the tail -- there needs to be another half to say that the result is 0 when the argument cannot match x:xs, i.e., when the argument is an empty list.

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

In the second case, the possibility of different patterns matching is just made a bit more epxlicit via case.

在第二种情况下,不同模式匹配的可能性只是通过案例更加明确。

BTW, laziness is not a key issue here -- ML, with eager evaluation but pattern matching much like Haskell, would work very similarly. Looks like pattern matching is what you really need to brush up about.

顺便说一句,懒惰不是一个关键的问题 - ML,热切的评估,但模式匹配很像Haskell,将工作非常相似。看起来模式匹配是你真正需要了解的。

#4


First of all the first example should be like this (edit: it looks like you fixed it now):

首先,第一个例子应该是这样的(编辑:它看起来像你现在修复它):

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

It works like this: say I give it a list with three items, it returns one plus the length of the tail (which is one plus the length of the tail (which is one plus the length of the tail, (which is [] at this point), which is 1), which is w), which is 3 (the final answer). Maybe nested parenthesis will help you understand it. ;-)

它的工作方式如下:说我给它一个包含三个项目的列表,它返回一个加上尾部的长度(这是一个加上尾部的长度(这是一个加上尾部的长度,(是[]此时),即1),即w),即3(最终答案)。也许嵌套的括号将帮助您理解它。 ;-)

#5


It is instructive to look at what the type signatures of the functions would be. They could be:

查看函数的类型签名是有益的。他们可能是:

myLength :: [a] -> Int

In myLength, 1 is being added to the result of the recursive call to myLength, which is an Int, which in turn results in an Int.

在myLength中,1被添加到myLength的递归调用的结果中,这是一个Int,它反过来导致一个Int。

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

In splitLines, pre (a [Char]) is being prepended to the result of the case statement, which, from looking at the cases, is either the result of a recursive call to splitLines, which is [[Char]]; or an empty list. In both cases, prepending pre (a [Char]) will result in a [[Char]] in turn.

在splitLines中,pre(a [Char])被置于case语句的结果之前,从查看案例中,它是对splitLines的递归调用的结果,即[[Char]];或一个空列表。在这两种情况下,预先加上pre(a [Char])将依次产生[[Char]]。