【问题标题】:helper nested functions in CLCL 中的辅助嵌套函数
【发布时间】:2013-11-01 08:03:45
【问题描述】:

我曾经像这样在 Haskell 中编写嵌套辅助函数(顺便说一句,偶尔使用外部函数的参数并且是递归的)(loop):

sum a b = let
  loop s i = if i > b then s else loop (s + i) (i + 1)
  in loop 0 a

Common Lisp 中最清晰的类比是什么?

我在这里四处搜索,发现一些讨论集中在从函数返回函数(以及尝试调用此类“返回”函数时可能出现的问题),据我所知,情况并不完全相同。

【问题讨论】:

  • 虽然帮助函数在 Lisp 中很常见,但不鼓励以这种方式进行迭代。 Google coding standards tell you to prefer iteration over recursion。这当然不是您问题的答案,但我认为无论如何它可能会有用。
  • @wvxvw,在这种情况下使用 Haskell 的递归似乎是在尾部位置,就像在尾部位置调用名称时,Scheme 命名为 let 一样。它最终成为首选或尾随,例如变相迭代。需要注意的是,Common Lisp 不保证尾调用,因此需要明确使用迭代方法来避免堆栈溢出。
  • @PauloMadeira 好吧,OP 用 Common Lisp 编写。这绝对不是您在 CL 中迭代的方式。在旁注中,我认为答案中的 Lisp 代码下意识地修复了它:OP 可能意味着 ... then s else loop ... 而不是 ... then sum else loop ...。 OP 的版本无法在 Haskell 中编译。 GHC 会将此理解为无限类型。
  • @wvxvw 感谢您的 cmets!作为一个 CL 菜鸟,我非常感谢他们。我对选择尾递归的解释……嗯,说来话长。我们实际上在研究函数式编程的原理。在我大学的 Lisp 上。我担任助教,而 Lisp 是由讲师选择的。 (我更喜欢 Haskell。)我使用 Touretzky 的关于 Lisp 的书,他也给出了 DO-stuff 之前的递归示例。
  • 是的,出于某种原因,坚持认为 CL 是一种函数式语言,或者说它是学习 FP 的良好候选者是很常见的(嗯,在某种“常见”意义上)。我不是 FP 的忠实粉丝,尤其不是 Haskell :) 但 CL 不是一个好的选择/对于 FP 来说非常不习惯。有一些库可以使 CL 中的函数式编程更容易接受(函数式容器、用于常见习语的辅助函数,例如组合、柯里化等),但它们的存在归功于上述混淆的事实。 CL 是关于编程舒适度、元编程和研究的。

标签: common-lisp nested-function


【解决方案1】:

Labels用于定义局部函数。

CL-USER> (defun thing (x)
           (labels ((helper (y) (loop for i from x to y collect i)))
             (helper 5)))
THING
CL-USER> (thing 1)
(1 2 3 4 5)

这有点像函数的 let* 语句,因为您可以定义多个本地函数。 所以这里我们定义了 helper 和 double-it。

(defun thing (x)
   (labels ((helper (y) (loop for i from x to y collect i))
            (double-it (num) (* 2 num)))
     (helper (double-it 10))))

您也可以使用 lambda。在这种情况下,它相当整洁,尽管在这种情况下我仍然更喜欢标签的可读性。

CL-USER> (defun another-test (x)
           (funcall #'(lambda (y) (loop for i from x to y collect i)) 10))
ANOTHER-TEST

CL-USER> (another-test 2)
(2 3 4 5 6 7 8 9 10)

标签也可以递归使用:

CL-USER> (defun test-recurse (x)
           (labels ((rec-list (count) 
                      (when (< count x) 
                        (cons count (rec-list (+ 1 count))))))
             (rec-list 0)))
TEST-RECURSE
CL-USER> (TEST-RECURSE 10)
(0 1 2 3 4 5 6 7 8 9)

希望对你有帮助!

【讨论】:

  • 我试图在由标签定义的函数中使用递归:clisp 抱怨:EVAL:函数 IMPL 未定义(我翻译的本地化消息)。这里 impl 是用标签递归定义的函数。
  • @ArtemPelenitsyn Haskell 没有非递归绑定的概念(但它存在于类似的语言中,例如 OCaml),这将是 let reclet 之间的区别。在 Common Lisp 中也有这个区别,它使用labels 类似于let recflet 类似于let
  • @wvxvw:为澄清干杯,我期待学习haskell,但还没有这样做
  • Artem,可能导致您的错误的最可能错误是将对 impl 的调用放在标签表单之外而不是在其中。检查你的语法,寻找错别字。
  • @m-n 是的,我在标签之外拨打了 impl 的电话!非常感谢!
【解决方案2】:

如果你的 Lisp 优化了尾调用,标签和命名的 let

就像玩“愚蠢的 Lisp 技巧”一样,我要指出,在 Scheme 中,类似于

sum a b = let
  loop s i = if i > b then sum else loop (s + i) (i + 1)
  in loop 0 a

是 letrec 或命名的 let:

(define (sum a b)
  (letrec ((loop (lambda (s i)
                   (if (> i b)
                       s
                       (loop (+ s i) (+ i 1))))))
    (loop 0 a)))

(define (sum a b)
  (let loop ((s 0) (i a))
    (if (> i b)
        s
        (loop (+ s i) (+ i 1)))))

Letrec,因为 Scheme 是一个 Lisp-1,给你labels 的功能,Baggers described。命名的 let 可以在 Common Lisp 中使用 labels 周围的宏来完成:

(defmacro named-let (name bindings &body body)
  `(labels ((,name ,(mapcar 'first bindings)
              ,@body))
     (,name ,@(mapcar 'second bindings))))

(pprint (macroexpand
 '(named-let loop ((s 0) (i a))
   (if (> i b)
       s
       (loop (+ s i) (+ i 1))))))

;; (LABELS ((LOOP (S I)
;;            (IF (> I B)
;;                S
;;                (LOOP (+ S I)
;;                      (+ I 1)))))
;;   (LOOP 0 A))

Do 和 Do* 应该在任何地方都有效

然而,尾调用不一定在 Common Lisp 中被优化掉,所以这种用于迭代的递归并不常见。迭代模拟为do

(defun sum (a b)
  (do ((s 0 (+ s i))
       (i a (+ i 1)))
      ((> i b) s)))

循环也可以

您也可以使用loop,但它有点冗长(但如果您熟悉do,也可能更具可读性:

(defun sum (a b)
  (loop
     for s = 0 then (+ s i)
     for i = a then (+ i 1)
     when (> i b) return s))

【讨论】:

  • 感谢您提供的替代方案列表,感谢您为您的回答 +1。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多