Haskell - Lambda演算相当的语法?

5
在编写 Haskell 中的一些 Lambda 函数时,我最初编写函数如下:
tru = \t f -> t
fls = \t f -> f

然而,我很快就从在线示例中注意到这样的函数经常被编写成以下形式:
tru = \t -> \f -> t
fls = \t -> \f -> f

具体来说,传递给该函数的每个项目都有自己的\->,与上面的情况不同。当检查它们的类型时,它们似乎是相同的。我的问题是,它们是否等效或实际上存在某些差异?并且不仅对于这两个函数,对于一般的函数来说,这是否有所区别?非常感谢!

2个回答

14

它们是相同的,Haskell会自动柯里化来保持语法的美妙统一。以下所有的表达都是等价的。

foo a b = (a, b)
foo a = \b -> (a, b)
foo = \a b -> (a, b)
foo = \a -> \b -> (a, b)
-- Or we can simply eta convert leaving
foo = (,)

如果你想要符合语言习惯,更倾向于使用第一个或最后一个。引入不必要的 lambda 表达式有助于教学柯里化,但在实际代码中只会增加语法上的混乱。

然而,在原始的 lambda 演算(不是 Haskell),大多数人手动进行柯里化。

\a -> \b -> a b

因为人们不会手写太多的λ演算,而且当他们这样做时,他们倾向于使用未加糖的λ演算来保持简单。

** 模掉单态限制(monomorphism restriction),但对于带类型签名的情况下也不会影响你。


我应该指出,虽然我更喜欢第一个或最后一个用于大多数事情,但第二个有时也是一种替代方案,例如在定义运算符时。你可以想象做类似这样的事情:f . g = \x -> f (g x) - kqr
1
这是正确的,但转换并不是柯里化;它只是语法糖。柯里化是将 (a,b) -> c 转换为 a -> (b -> c)(因此 curry :: ((a,b) -> c) -> a -> b -> c)。确实,这种符号表示法旨在简化编写柯里化函数。(这可能是您想要表达的,但我认为值得明确说明一下。) - Antal Spector-Zabusky

6

虽然,如jozefg所说,它们本身是等效的,但当与局部变量绑定组合使用时,它们可能会导致不同的执行行为。请考虑

f, f' :: Int -> Int -> Int

用这两个定义:
f a x = μ*x
 where μ = sum [1..a]

并且

f' a = \x -> μ*x
 where μ = sum [1..a]

这两个函数看起来是等价的,而且肯定会产生相同的结果。

GHCi,版本7.6.2:http://www.haskell.org/ghc/:输入“?”获取帮助
...
[1 of 1] Compiling Main            ( def0.hs, interpreted )
Ok,模块已加载:Main。
*Main> sum $ map (f 10000) [1..10000]
2500500025000000
*Main> sum $ map (f' 10000) [1..10000]
2500500025000000

然而,如果你自己尝试一下,你会发现使用f需要花费很长时间,而使用f'则能立即得到结果。原因是f'的形式促使GHC编译它,以便实际上在将其映射到列表之前评估f' 10000。在这一步中,计算并存储了值μ(f' 10000)的闭包中。另一方面,f被简单地视为“两个变量的一个函数”;(f 10000)仅被存储为包含参数10000的闭包,而μ根本没有被计算。只有当map(f 10000)应用于列表中的每个元素时,才会计算整个sum [1..a],这需要一些时间对于[1..10000]中的每个元素。使用f',这是不必要的,因为μ已经预先计算了。

原则上,公共子表达式消除是GHC能够自行完成的优化,因此你可能会在像f这样的定义中获得良好的性能。但是你不能真正依赖它。


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