热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在顶层使用标签与helperandmain与CommonLisp中的嵌套defun之间的比较。哪个最好?

我正在尝试通过《CommonLisp:符号计算的温和介绍》一书来学习CommonLisp。此外,我正在使用SBCL、Emacs和Slime。在

我正在尝试通过《Common Lisp:符号计算的温和介绍》一书来学习 Common Lisp 。此外,我正在使用 SBCL、Emacs 和 Slime。

在第 8 章的高级部分,作者介绍了labels特殊功能。实际上,他将在顶层(主函数和辅助函数)上定义事物与label在函数内部使用表达式进行了对比。

例如,这将是一个reverse使用顶级方法进行尾调用的列表函数:

(defun reverse-top-level-helper (xs-left accu)
(cond ((null xs-left) accu)
(t (reverse-top-level-helper (cdr xs-left)
(cons (car xs-left)
accu)))))
(defun reverse-top-level-main (xs)
(reverse-top-level-helper xs nil))

另一方面,下面的代码将使用labels

(defun reverse-labels (xs)
(labels ((aux-label (xs-left accu)
(cond ((null xs-left) accu)
(t (aux-label (cdr xs-left)
(cons (car xs-left) accu))))))
(aux-label xs nil)))

因此,标签方法避免了人们将顶层的辅助函数搞砸的机会。与顶级方法不同,标签方法可以访问主函数的局部变量。

不幸的是,根据作者的说法,在大多数 lisp 实现中,没有办法跟踪标签表达式中的函数。这似乎是我的情况,因为我是从 REPL 得到的:

CL-USER> (trace aux-label)
WARNING: COMMON-LISP-USER::AUX-LABEL is undefined, not tracing.
NIL

吸引我的一点是,作者没有展示在 Racket 中很常见的第三种方法。我将其称为嵌套 defuns

同样的问题将被解决为:

(defun reverse-racket-style (xs)
(defun aux (xs-left accu)
(cond ((null xs-left) accu)
(t (aux (cdr xs-left) (cons (car xs-left) accu)))))
(aux xs nil))

在这种方法中,辅助函数可以从主函数访问局部变量。它也可以通过 REPL 进行跟踪。

我一整天都在使用它。所以我知道它可以在一个文件中使用它的许多功能。实际上,我什至不知道 trace 是如何工作得这么好,因为我使用了一堆不同的辅助函数,并且所有这些函数都aux在球拍样式下具有相同的名称。该trace知道AUX我想看到的。

最重要的是,这种遗漏真的让我很感兴趣。特别是因为我真的很喜欢这本书。我想我可能错过了一些东西。

1 - 为什么没有提到这种方法?这种带有嵌套 defun 的“球拍风格”在 Common Lisp 中被认为是糟糕的风格吗?

2 - 我是否遗漏了一些重要的细节(例如,这种方法可能是难以找到错误或产生性能问题的根源)?

3 - 这种遗漏是否有一些历史原因?

回答

是的,有充分的理由。在 Racket 中,我们有define

在内部定义上下文中,define表单引入了本地绑定;请参阅内部定义。A 顶层,id在评估后创建顶层绑定expr

因此,正如您所说, adefine在局部上下文(例如函数体)中定义了一个局部函数,可以访问封闭变量并且仅在该函数期间存在。

现在将其与 Common Lisp 的进行比较 defun

全局环境中定义一个名为function-name的新函数

因此,无论 adefun出现在哪里,它总是在全局范围内定义一个名称,不能访问局部变量,并且名称在全局范围内可用。因此,您对嵌套的建议defun实际上等同defun于在顶级定义 the (从名称在顶级可用的意义上说,并且在局部变量不可访问的意义上),只是名称不存在直到您至少调用了一次原始函数,坦率地说,这是相当不直观的行为。

顺便说一下,这种labels方法是你想要的。在 Common Lisp 中,如果我们想要局部辅助函数,我们使用flet(对于非递归函数)或labels(对于递归函数)。

至于为什么会这样,Common Lisp 始终试图强制执行一个非常明确的变量范围。在任何函数中,局部变量都是用(let ...)和引入的,并且只存在于块内部,局部函数是用(flet ...)和引入的(labels ...)。Racket 具有类似的构造,但也允许使用更类似于 Scheme 的范式(毕竟 Racket 是一种 Scheme 方言),define用于为当前范围的其余部分定义局部变量,类似于您在更多命令式语言中的做法。





回答

不要写嵌套defuns.

编写嵌套的 defuns 通常是一个错误。defun(和大多数其他defsomething运算符)被认为在顶级使用。顶级通常意味着作为最顶层的表达式或仅嵌套在prognor 中eval-when。然后文件编译器将识别这些宏。

作为嵌套函数,编译器无法识别defun. 调用外部函数将在每次调用时和全局定义内部函数。

例子:

(defun foo ()
(defun bar ()
20))
(defun baz ()
(defun bar ()
30))

现在做:

(bar) ; -> error undefined function BAR
(foo)
(bar) ; -> 20
(baz)
(bar) ; -> 30
(foo)
(bar) ; -> 20
(baz)
(bar) ; -> 30
...

哎呀!BAR每次调用FOO和 时,全局函数都会被覆盖BAZ

嵌套函数

使用FLETLABELS来定义局部函数。

DEFUN不能定义本地词汇功能。它定义了以符号为名称的全局函数。

CL-USER 77 > (defun one ()
(defun two ()
40))
ONE
CL-USER 78 > (fboundp 'two)
NIL
CL-USER 79 > (one)
TWO
CL-USER 80 > (fboundp 'two)
T

跟踪本地函数

(trace aux-label)

以上通常不是跟踪本地函数的方式。该语法跟踪全局函数。

要跟踪本地函数,请参阅您的 Lisp 实现手册以获取trace宏的文档。它可能支持跟踪本地函数,但有一个特殊的语法来这样做。






推荐阅读
author-avatar
lnssm
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有