是否可以使用连续传递样式将此递归函数转换为尾递归?

时间:2019-02-14 13:51:30

标签: recursion f# monads tail-recursion

我最近写了一个ETL,它工作正常。 我想提醒自己如何使用免费的monad,因此想转换我的ETL。 注意:我的目的不是写更好的ETL,而是让自己重新熟悉免费的monad。在重新学习免费monad的工作原理时,我一直跟踪这个问题的主题。

几个月前,我问了related question。有人评论说,我的递归函数可以使用连续传递样式使成为尾递归。我不知道该怎么做。

一些示例代码:

type In1 = int
type In2 = int
type Out1 = int
type Out2 = int

type FaceInstruction<'a> =
| Member1 of (In1 * (Out1 -> 'a))
| Member2 of (In2 * (Out2 -> 'a))

let private mapI f = function
    | Member1 (x, next) -> Member1 (x, next >> f)
    | Member2 (x, next) -> Member2 (x, next >> f)

type FaceProgram<'a> =
| Free of FaceInstruction<FaceProgram<'a>>
| Pure of 'a

let rec bind f = function
| Free x -> x |> mapI (bind f) |> Free
| Pure x -> f x

我要使尾部恢复原状的函数是bind

我的尝试看起来像

let rec bind2 (f: 'a -> FaceProgram<'b>) k  z : FaceProgram<'b> = 
    match z with
    |Pure x -> x |> f |> k
    |Free x -> bind2 ???

我开始认为,实际上不可能使此尾部递归。类型FaceInstruction<'a>已经包含一个延续,并且函数mapI修改了该延续,因此现在尝试添加另一个延续k是我现在无法处理的两个延续之一! / p>

1 个答案:

答案 0 :(得分:2)

实际上,bind实际上不是递归函数,因为在任何给定时间,在堆栈中对bind的调用永远不会超过一个。

原因是因为bindmapI都不调用bind。请注意,它们如何立即退出而不深入堆栈。 bind调用mapI,但是mapI根本不调用任何函数(除了Member1Member2这是构造函数之外)。他们所做的是使用bindnext组成一个新的Free monad实例。必须将bind声明为rec,因为它需要自我引用才能将自身作为参数传递给mapI

需要将其解释为尾递归,这应该不太困难。