Haskell函数定义语法

3

我正在以以下方式进行列表连接(使用GHC作为示例):

myConcat :: [[a]] -> [a]
myConcat xs = foldr (++) [] xs
myConcat    = foldr (++) []

请问有人能解释一下为什么上面的定义可以工作,而这个却不能吗?

myConcat xs = foldr (++) []

这段代码的最后一行是故意不允许的吗(比如因为构造可能变得混乱,没有用等原因),还是有更深层次的原因,也许与柯里化有关...我希望我能为此提供一些启示,这真的让我感到困惑 :/
后来编辑:除了下面给出的解释外,我发现"部分函数应用和柯里化"在书籍"Real World Haskell"的第4章“函数式编程”中是一个很好的信息来源。该书可以免费在线获取。
3个回答

7

让我们来回顾一下不同的版本:

myConcat xs = foldr (++) [] xs

这是通常的方式,提供一个参数,被foldr所使用。类型为[[a]] -> [a],因为我们在左侧有一个类型为[[a]]的参数,当被传递到右侧时会产生[a]结果。

myConcat = foldr (++) []

这里 foldr 被部分应用,因此我们返回一个函数,它可以接受一个额外的参数——一个列表的列表。所以我们从右边得到的已经是我们需要的东西,它不是一种 "语法糖",而是另一种表达与第一个版本相同的方式。类型仍然是 [[a]] -> [a]:左边没有任何东西,但在右边返回了一个该签名的函数。

myConcat xs = foldr (++) []

这里foldr也被部分应用了,我们返回一个函数,可以像之前一样接受一个参数,但是我们的定义有一个额外的参数xs,它在右侧没有使用。编译器不知道我们想要将它应用到右侧。类型为t -> [[a]] -> [a]。为什么呢?
假设你有一个平方函数:
sqr :: Int -> Int 
sqr x = x*x

你所做的实质上与提供一个未使用的附加参数相同:
sqr:: Int -> t -> Int 
sqr x y = x*x

这个函数仍然“工作”,例如 sqr 3 "bla" 会得到9,但是类型标记是错误的,未使用的参数是... 嗯,未使用的。未使用的参数没有固定的类型,因为它可以是几乎“任何东西”,所以在签名中使用了类型变量(t)。


+1:非常易懂的解释;你有没有写Haskell相关博客的习惯?我喜欢你的风格。 :-) - Frerich Raabe
@Frerich Raabe:谢谢!事实上我有一个博客,主要是关于Scala的,但偶尔也会涉及Haskell,但不幸的是它是用德语写的:http://dgronau.wordpress.com/ - Landei

3

好的,让我们来看一下柯里化函数foldr的类型签名:

>:t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b

因此,foldr 接受一个二进制函数(即 a->b->b),一个 b 值,一个 a 值的列表,并返回一个 b 值。

让我们也看一下 文档foldr 的定义以获得更明确的定义:

对于一个二元操作符、一个起始值(通常是运算符的右单位元素)和一个列表,foldr 从右到左使用二元操作符来缩减列表:

现在,让我们看一下 myConcat xs = foldr (++) [] 的类型签名。

> :t myConcat
myConcat :: t -> [[a]] -> [a]

嗯...这不是我们想要的...

问题在于您从未提供类型为[a]的值给foldr。因此,现在myConcat需要一些值(任何类型),以满足xs并完成foldr (++) [],例如:

> myConcat 2 [[1,2],[3,4]] 
[1,2,3,4]
> myConcat Nothing [[1,2],[3,4]] 
[1,2,3,4]

这样做是可行的,但第一个参数是多余的。

然而,如果我们将xs值传递给foldr (++) [],就像这样:

myConcat xs = foldr (++) [] xs

并检查其类型签名

> :t myConcat
myConcat :: [[a]] -> [a]

啊,好多了。现在myConcat使用xs来完成foldr函数。
另外,myConcat = foldr (++) []也可以工作,并且实际上是无参式编程的一个例子。如果我们检查foldr (++) []的类型签名,
> :t foldr (++) []
foldr (++) [] :: [[a]] -> [a]

由于我们已经通过部分应用提供了foldr的前两个参数,因此我们得到了一个函数,它将接受一个[[a]]值并执行我们想要的操作!所以我们只需将其赋值给一个名称,它就像上面的示例一样工作,但我们不需要显式传递参数!

> let myConcat = foldr (++) []
> :t myConcat
myConcat :: [[a]] -> [a]
> myConcat [[1,2],[3,4]]
[1,2,3,4]

2
myConcat xs = foldr (++) []

它的类型为t -> [[a]] -> [a],这与另外两个的类型[[a]] -> [a]不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接