【问题标题】:User-friendly wrappings for tail recursive functions尾递归函数的用户友好包装
【发布时间】:2020-01-09 10:34:51
【问题描述】:

我正在努力学习普通的 lisp。我熟悉尾递归,但我不熟悉以调用者不必初始化累加器变量的方式包装尾递归函数的惯用方式。这是一个例子:

(defun add-em (n s) 
    (if (eql n 0) 
        s 
        (add-em (- n 1) (+ s n))
    )
) 

假设我想包装这个函数,这样用户只需要管理输入n,而不需要完整的函数调用(add-em <number> 0)。在其他语言中,比如 scala,我会定义一个内部函数,然后在外部函数的末尾,我会调用尾递归内部函数来运行算法。

在普通的 lisp 中,我可以在函数中定义一个 lambda 并使用它,但它看起来有点难看。我认为可能有一种更惯用的方式来做到这一点,但谷歌搜索并没有真正给我任何结果。

除了完全拆分函数之外,还有更惯用的方法吗?或者这是最好的方法?一个例子:

(defun add-em-inner (num sum)
       (if (eql num 0)
       sum
       (add-em-inner (- num 1) (+ num sum))
       )
)

(defun add-em (n) 
  (add-em-inner n 0)
)

【问题讨论】:

    标签: recursion lisp sbcl


    【解决方案1】:

    一种方法是使用labels 运算符来定义一个递归的词法函数。也就是说:

    (defun add-em-inner (num sum)
           (if (eql num 0)
               sum
               (add-em-inner (- num 1) (+ num sum))))
    
    (defun add-em (n) 
      (add-em-inner n 0))
    

    变成这样:

    (defun add-em (n) 
      (labels ((add-em-inner (num sum)
                 (if (eql num 0)
                   sum
                   (add-em-inner (- num 1) (+ num sum)))))
        (add-em-inner n 0)))
    

    如果您不介意将额外的累加器作为函数公共接口的一部分,而只关心用户的便利性(调用者不必指定值),则可以将其设为可选参数:

    (defun add-em (n &optional (s 0)) 
        (if (eql n 0) 
            s 
            (add-em (- n 1) (+ s n)))) 
    

    通常有充分的理由不这样做;例如,您可能希望保留为向后兼容的未来 API 扩展定义可选参数的能力。这在这里仍然是可能的,但前提是外部调用者不传递该参数。

    【讨论】:

    • 使用本地函数,你也可以只检查一次类型,比如这里的n应该是非负数。
    • optional 方法还允许用户在需要时指定非默认累加器。例如,可以通过将所有先前列表的总和作为下一个列表的初始累加器来对数字列表进行求和。用户可以在不感兴趣时​​忽略s - 或者在他们感兴趣时指定它 - win win。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-14
    • 1970-01-01
    • 2013-12-17
    • 2017-10-14
    • 2020-01-01
    • 1970-01-01
    • 2012-12-28
    相关资源
    最近更新 更多