2015-10-20 135 views
1

这是从教学的例子来说明CPS和尾递归:需要帮助了解连续函数

fun sum [] k = k 0 
    | sum (x::xs) k = sum xs (fn y=>k(x+y)); 

我理解如何匿名函数fn y=>k(x+y)将正确总结输入列表中的元素问题。

据我所知,匿名函数意味着一个带有一个参数y的新函数,其中函数体调用原函数k,参数为y+x

如果我调用sum [1,2,3,4,5] (fn x=>x);我得到15.如果我有sum [1,2,3,4,5] (fn x=>3x);答案是45。sum功能的用户,因此必须先了解sum确切的血淋淋的细节,因为只有中k适当的版本将产生总和给定列表。以这种方式提供用户提供的功能的真实目的是什么?

如果我是sum函数的作者,我无法控制用户通过k的内容。换句话说,我怎么甚至指定这个函数会精确地做什么?

+0

这是一个坏榜样,我认为:消费者不应该知道的是'k'就是实现这样的细节,并且得到正确的根据功能合同的结果,他们必须通过身份识别功能。 “适当的”解决方案根本不会暴露'sum'签名中的'k'参数。 – zerkms

回答

1

我在理解如何正确地总结输入列表的元素时遇到问题。

尝试,用手评估你的函数:

sum [1,2,3] id 
sum [2,3] (fn y1=>id(1+y1)) 
sum [3] (fn y2=>(fn y1=>id(1+y1))(2+y2)) 
sum [] (fn y3=>(fn y2=>(fn y1=>id(1+y1))(2+y2))(3+y3)) 
(fn y3=>(fn y2=>(fn y1=>id(1+y1))(2+y2))(3+y3)) 0 
(fn y2=>(fn y1=>id(1+y1))(2+y2))(3+0) 
(fn y2=>(fn y1=>id(1+y1))(2+y2)) 3 
(fn y1=>id(1+y1))(2+3) 
(fn y1=>id(1+y1)) 5 
id(1+5) 
id(6) 
6 

所以你可以看到,这个函数建立起匿名函数在堆内存链,最终相互调用。一个正常的递归函数会使用等价的堆栈空间。

sum函数的用户因此必须首先理解sum的确切血统细节,因为只有适当版本的k将产生给定列表的总和。以这种方式提供用户提供的功能的真实目的是什么?

正如Bergi写道的,用户不需要了解sum函数的工作方式,只需要继续作为参数并在其基本情况下解决它。正如Bergi也写到的那样,它不必评估其基本情况下的k值。这个函数另一种方法是:

fun sum [] k = k 
    | sum (x::xs) k = sum xs (fn y=>k(x+y)); 

一个应用在这里,和对于用一个回调作为参数和返回值输出求和函数的理由,就是你可以懒洋洋链职能一起这样。例如,通过上面的函数,您可以对列表进行求和;

fun sumMany [] k = k 
    | sumMany (xs::xss) k = sumMany xss (sum xs k) 

,你可能会评估它像

val result = sumMany [[1,2,3],[4,5,6],[7,8,9]] (fn x=>x) 0 
+0

谢谢!对这一系列匿名功能的手动评估正是我所寻求的。对我而言,它不直观,难以阅读,并且很难检查它是否能产生理想的结果。所有这些似乎都与FP所吹捧的相反。 –

3

总和功能的用户,因此必须先了解sum仅作为k合适的版本准确的血淋淋的细节会产生定列表的总和。

不可以。与往常一样,阅读文档应该足够了,不需要查看实现细节。你的k给出清单的确切总和 - 这就是所有重要的。你应该明白k就像output parameter(尽管没有突变);它基本上是一个callback

如果我SUM函数的作家,我无法控制哪些用户会通过在k。换句话说,我怎么甚至指定这个函数会精确地做什么?

您不需要关心用户通过什么。您只需记录该功能的功能:使用提供的xs列表的总和调用提供的k。这个回报值并不重要。

以这种方式提供用户提供的功能的真实目的是什么?

如果出现极端情况,您不需要返回continuation-passing style中的任何值 - 只需将它传递给回调即可。这使得调用堆栈变得多余。从另一个角度来看,每个函数都会在尾部调用中结束,可以优化而不是返回。