如何转换CPS样式的gcd计算以使用Continuation Monad

时间:2015-09-19 12:21:12

标签: ocaml monads continuations

让我们考虑一下Continuation monad的以下实现,用于CPS式计算产生和整数:

module Cont : sig
  type 'a t = ('a -> int) -> int
  val return : 'a -> 'a t
  val bind : 'a t -> ('a -> 'b t) -> 'b t
  val callCC: (('a -> 'b t) -> 'a t) -> 'a t
end = struct
  type 'a t = ('a -> int) -> int

  let return x =
    fun cont -> cont x

  let bind m f =
    fun cont -> m (fun x -> (f x) cont)

  let callCC k =
    fun cont -> k (fun x -> (fun _ -> cont x)) cont
end

我们如何重写CPS风格的gcd计算实现(参见How to memoize recursive functions?),特别是memoization以利用Cont monad?

定义后

let gcd_cont k (a,b) =
  let (q, r) = (a / b, a mod b) in
  if r = 0 then Cont.return b else k (b,r)

我尝试使用类型求解器给出关于memoization函数应该具有的类型的提示:

# let gcd memo ((a,b):int * int) =
  Cont.callCC (memo gcd_cont (a,b)) (fun x -> x)
;;
    val gcd :
  (((int * int -> int Cont.t) -> int * int -> int Cont.t) ->
   int * int -> (int -> 'a Cont.t) -> int Cont.t) ->
  int * int -> int = <fun>

但是我无法将此提示转换为实际实现。有人能够这样做吗?在memoization函数中使用“callCC”的逻辑是,如果在缓存中找到一个值,那么这是一个提前退出的条件。

1 个答案:

答案 0 :(得分:3)

我觉得问题在于,在他对How to memoize recursive functions?的回答中,迈克尔称CPS风格不是CPS风格。在CPS样式中,只要想要返回一个值,就会使用额外的延续参数k - 然后将该值应用于k

这不是我们想要的,而不是实现的:

let gcd_cont k (a,b) =
  let (q, r) = (a / b, a mod b) in
  if r = 0 then b else k (b,r)

这里,k不用于返回(直接返回b),而是使用它而不是执行递归调用。这会解除递归:在gcd_cont内,可以将k视为gcd_cont本身,就像使用let rec一样。稍后,gcd_cont可以使用一个固定点组合器变成一个真正的递归函数,基本上“将它提供给自己”:

let rec fix f x = f (fix f) x
let gcd = fix gcd_cont

(这相当于Michael定义的call函数)

直接使用gcd定义let rec的不同之处在于,具有未递归递归的版本允许一个“检测”递归调用,因为递归本身由fixpoint combinator执行。这就是我们想要的memoization:我们只想在结果不在缓存中时执行递归。因此,memo组合子的定义。

如果使用let rec定义函数,则递归在定义函数的同时关闭,因此无法检测递归调用点以插入memoization。

作为旁注,这两个答案基本上实现了同样的事情:唯一的区别是它们在fixpoint combinator中实现递归的方式:Michael的fixpoint combinator使用let rec,Jackson的使用引用,即“Landin's结“ - 如果你的语言有参考文献,那么实现递归的另一种方法。

Sooo,总而言之,我会说实现在延续monad中并不是真的可行/没有真正意义,因为事情首先不是CPS。