我正在通过阅读“又一个Haskell教程”这本书来学习haskell,而且我遇到了一个问题,当涉及到contiuation传递风格。这本书给出了一个cps折叠:
cfold’ f z [] = z
cfold’ f z (x:xs) = f x z (\y -> cfold’ f y xs)
并给出测试结果:
CPS> cfold (+) 0 [1,2,3,4]
10
CPS> cfold (:) [] [1,2,3]
[1,2,3]
但是,当我尝试测试时,我发现存在问题,ghci给出了:
*Main> cfold (+) 0 []
<interactive>:8:7:
Occurs check: cannot construct the infinite type:
t10 = (t10 -> t10) -> t10
Expected type: t10 -> t10 -> (t10 -> t10) -> t10
Actual type: t10 -> t10 -> t10
In the first argument of `cfold', namely `(+)'
In the expression: cfold (+) 0 []
In an equation for `it': it = cfold (+) 0 []
这对我来说很有意义,因此我将cps的定义更改为类似的内容 这样:
cfold f z [] = z
cfold f z (x:xs) = (\y -> cfold f y xs) (f x z)
它运作良好:
*Main> cfold (+) 0 [1,2,3]
6
所以我的问题来了,是书中的错误还是我想念的东西?
答案 0 :(得分:9)
我认为书中没有错误。本书中的函数cfold'
在contiuation传递样式中采用函数,因此传递给cfold'
的函数必须采用3个参数:累加器,当前列表元素和继续,要传递给它结果。
(+)
只接受两个参数,因为它不是cps样式,所以你不能使用cfold'函数。要使用cfold'对列表的元素求和,您可以写:
> cfold' (\e acc cont -> cont (acc + e)) 0 [3,2,4]
9
请注意,lambda采用三个参数:元素,累加器和延续。根据当前计算的结果,延续是一个表示“下一步该做什么”的函数。因此,通过每次调用continuation,您将处理下一个元素。
那么,当我不打电话给延续时,您认为会发生什么?我可以停止处理列表,实现像takeWhile这样的东西吗?
> cfold' (\acc e cont -> if e <= 3 then cont (e : acc) else acc) [] [1,2..]
[3,2,1]
在此示例中,当元素大于3时,我们不调用continuation,因此我们停止处理列表。否则,我们将元素添加到累加器,这就是列表反向出现的原因。如示例所示,这适用于无限列表。
并且,您还可以编写一个函数,将任何 2参数函数(非cps样式)转换为cps样式:
toCps2 :: (a -> b -> c) -> (a -> b -> (c -> d) -> d)
toCps2 f = \a b c -> c (f a b)
此函数只传递将非cps函数应用于continuation的结果。您可以使用此函数来实现您的cfold,它采用非cps样式的函数来折叠列表:
cfold :: (a -> b -> c) -> b -> [a] -> c
cfold f = cfold' (toCps2 f)
使用此功能,您现在可以使用(+)折叠列表:
> cfold (+) 0 [3,2,4]
9
答案 1 :(得分:2)
又一个Haskell教程也是 cfold 的定义
cfold f z l = cfold' (\x t g -> f x (g t)) z l
此功能 cfold 正在使用 cfold'
在WinHugs环境中,它运行正常。