Haskell中curry、uncurry及函数组合的求值与报错疑问
(curry . uncurry) (+) 1 2的求值逻辑,以及(uncurry . curry) (+) (1,2)的错误原因 这个问题核心是要搞懂Haskell里函数组合(.)、curry/uncurry的定义,还有函数应用的优先级——咱们一步步拆解就清楚了。
先明确几个基础定义(方便对照):
-- 函数组合的标准定义 (.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (g x) -- uncurry:把接受两个独立参数的函数,转成接受元组的函数 uncurry :: (a -> b -> c) -> (a, b) -> c uncurry f (x, y) = f x y -- curry:把接受元组的函数,转成接受两个独立参数的函数 curry :: ((a, b) -> c) -> a -> b -> c curry f x y = f (x, y)
一、(curry . uncurry) (+) 1 2的具体求值步骤
Haskell里函数应用优先级最高,且是左结合的,所以这个表达式等价于(((curry . uncurry) (+)) 1) 2,咱们逐步展开:
先处理函数组合
curry . uncurry
根据.的定义,这个组合等价于\f -> curry (uncurry f)——它是一个高阶函数:先把输入的函数f传给uncurry,再把uncurry的结果传给curry。传入
(+)到组合函数
得到curry (uncurry (+)):(+)的类型是Num a => a -> a -> a,完全符合uncurry的参数要求(接受两个独立参数的函数),所以uncurry (+)会变成Num a => (a, a) -> a——也就是一个接受元组、返回两数之和的函数。- 接着把这个“吃元组”的函数传给
curry,curry会把它转回“吃两个独立参数”的函数。
传入第一个参数
1
根据curry的定义,curry f x等价于\y -> f (x, y)。这里f是uncurry (+),x是1,所以这一步得到的是一个lambda函数:\y -> uncurry (+) (1, y),而uncurry (+) (1,y)本质就是1 + y,所以这个lambda简化后就是\y -> 1 + y。传入第二个参数
2
把2代入lambda,得到1 + 2,最终结果为3。
你之前的疑惑点在于“uncurry需要元组输入”,但实际上curry的作用就是把后续的两个独立参数打包成元组,再传给uncurry处理——不是uncurry先执行,而是整个表达式是逐步展开的,uncurry直到curry完成元组打包后才会真正计算。
二、为什么(uncurry . curry) (+) (1,2)无法正常工作?
核心问题出在类型不匹配:
curry的参数要求是接受元组的函数(类型为((a,b)->c)),但(+)是一个接受两个独立参数的函数(类型为Num a => a->a->a),这俩类型完全不兼容。- 所以
curry (+)这一步本身就会触发类型错误——编译器会明确告诉你,无法将(+)适配成curry需要的“吃元组”的函数类型。 - 既然
curry (+)都无法通过检查,那么基于它的函数组合(uncurry . curry) (+)自然也无法执行,更别说后续传入(1,2)了。
简单总结:uncurry是给“吃两个独立参数”的函数用的,(+)刚好符合;而curry是给“吃元组”的函数用的,(+)根本不满足它的参数要求,这就是两者的本质区别。
内容的提问来源于stack exchange,提问作者freigeist

