代码之家  ›  专栏  ›  技术社区  ›  Chechy Levas

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

  •  0
  • Chechy Levas  · 技术社区  · 6 年前

    我最近写了一个ETL,工作得很好。 我想提醒自己如何使用免费单子,所以想转换我的ETL。 注意:我在这里的目的不是写一个更好的ETL,而是让自己重新熟悉免费的monads。在重新学习自由单子是如何工作的过程中,我偏离了这个问题的主题。

    所以我问 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> 已经包含一个continuation,并且函数 mapI 修改该延续,因此现在尝试添加另一个延续 k 是我现在无法处理的两个连续剧之一!

    0 回复  |  直到 6 年前
        1
  •  2
  •   AMieres    6 年前

    在现实中 bind 实际上不是递归函数,因为在堆栈中永远不会有多个调用 绑定 在任何时候。

    原因是 绑定 也不是 mapI 呼叫 绑定 . 注意它们是如何在不深入堆栈的情况下立即退出的。 绑定 电话 mapI 但是 mapI 根本不调用任何函数(除了 Member1 Member2 它们是构造函数函数)。他们所做的是使用 绑定 next . 绑定 必须声明为 rec 因为它需要一个自引用来将自身作为参数传递给 mapI .

    解释器需要实现为尾部递归,这应该不太困难。