未使用的参数
首先,将变量显式标记为未使用的方法是将变量替换为_
. 所以你真的想要:
treeFold (\_ ys -> 1 + sum ys)
你得到一个编译器错误,因为你写了:
treeFold (\ys -> 1 + sum ys)
...这不是一回事。
折叠
其次,我将对treeSize
示例树进行手动评估,以便您可以看到没有魔法发生:
treeSize (Tree 1 [Tree 2 [], Tree 3 []])
-- Inline definition of 'treeSize'
= treeFold (\_ ys -> 1 + sum ys) (Tree 1 [Tree 2 [], Tree 3 []])
-- Evaluate treeFold
= (\_ ys -> 1 + sum ys) 1 (map (treeFold (\_ ys -> 1 + sum ys)) [Tree 2 [], Tree 3 []])
-- Apply the anonymous function
= 1 + sum (map (treeFold (\_ ys -> 1 + sum ys)) [Tree 2 [], Tree 3 []])
-- Apply the 'map' function
= 1 + sum [ treeFold (\_ ys -> 1 + sum ys) (Tree 2 [])
, treeFold (\_ ys -> 1 + sum ys) (Tree 3 [])
]
-- Apply both 'treeFold' functions
= 1 + sum [ (\_ ys -> 1 + sum ys) 2 (map (treeFold (\_ ys -> 1 + sum ys)) [])
, (\_ ys -> 1 + sum ys) 3 (map (treeFold (\_ ys -> 1 + sum ys)) [])
]
-- Apply the anonymous functions
= 1 + sum [ 1 + sum (map (treeFold (\_ ys -> 1 + sum ys)) [])
, 1 + sum (map (treeFold (\_ ys -> 1 + sum ys)) [])
]
-- map f [] = []
= 1 + sum [ 1 + sum []
, 1 + sum []
]
-- sum [] = 0
= 1 + sum [1 + 0, 1 + 0]
= 1 + sum [1, 1]
-- Apply 'sum'
= 1 + 2
= 3
但是,有一种简单的方法可以记住treeFold
工作原理。它所做的一切都是Tree
用您提供的函数替换每个构造函数。
因此,如果您有:
Tree 1 [Tree 2 [Tree 3 [], Tree 4[]], Tree 5 [Tree 6 [], Tree 7 []]]
...并且您申请treeFold f
,它将评估为:
f 1 [f 2 [f 3 [], f 4 []], f 5 [f 6 [], f 7 []]]
treeSum
只是特殊情况f = (\_ ys -> 1 + sum ys)
:
1 + sum [1 + sum [1 + sum [], 1 + sum []], 1 + sum [1 + sum [], 1 + sum []]]
= 7
咖喱
最后一点是在 Haskell 中柯里化是如何工作的。当您定义如下函数时:
foo x y = x + y
...编译器实际上对两个嵌套函数进行了脱糖:
foo = \x -> (\y -> x + y)
这就是为什么您可以在 Haskell 中仅将函数部分应用于一个参数的原因。当您编写foo 1
时,它转换为:
foo 1
= (\x -> (\y -> x + y)) 1
= \y -> 1 + y
换句话说,它返回一个参数的新函数。
在 Haskell 中,所有函数都只接受一个参数,我们通过返回新函数来模拟多个参数的函数。这种技术被称为“currying”。
如果您更喜欢更传统的主流语言的多参数方法,您可以通过让函数接受元组参数来模拟它:
f (x, y) = x + y
然而,这并不是真正地道的 Haskell,它不会给你任何形式的性能改进。