使用 # 又名读取宏



阅读Doug Hoyte的"Let Over Lambda"一书,我发现了#.符号的以下描述,又名read-macro:

COMMON LISP 内置的基本读取宏是 #. 读取时评估宏。此读取宏允许您将对象嵌入到您读取的表单中,这些对象无法序列化,但可以使用一些 lisp 代码创建。

它来自第4章,本书的大部分内容可以在这里找到:http://letoverlambda.com/index.cl/toc

这是书中的例子,展示了每次如何以不同的方式阅读相同的表达式:

* '(football-game
     (game-started-at
       #.(get-internal-real-time))
     (coin-flip
       #.(if (zerop (random 2)) 'heads 'tails)))
(FOOTBALL-GAME
  (GAME-STARTED-AT 187)
  (COIN-FLIP HEADS))
* '(football-game
     (game-started-at
       #.(get-internal-real-time))
     (coin-flip
   #.(if (zerop (random 2)) 'heads 'tails)))
(FOOTBALL-GAME
  (GAME-STARTED-AT 309)
  (COIN-FLIP TAILS))

接下来,作者演示了一些硬核技巧,使用宏创建变体#

因此,事实证明,#'也是某种读取宏,它通常在表示函数名称的符号之前使用。但是有必要吗,他在那里的工作到底是什么?

我可以为高阶函数放置符号,无论是否带有#'

CL-USER> (defun test nil t)
TEST
CL-USER> (funcall #'test)
T
CL-USER> (funcall 'test)
T

同样的成功。

您可以通过两种方式调用函数的全局定义:

CL-USER> (defun test nil t)
TEST
CL-USER> (funcall #'test)
T
CL-USER> (funcall 'test)
T

但请看这个:

CL-USER 10 > (defun foo () 42)
FOO
CL-USER 11 > (flet ((foo () 82))
               (print (foo))
               (print (funcall 'foo))
               (print (funcall #'foo)))
82   ; the local definition
42   ; the symbol references the global definition
82   ; (function foo) / #'foo  references the local definition

(funcall 'foo)从符号中查找函数。

(funcall #'foo)从词法环境中调用函数。如果没有,则使用全局定义。

#'foo(function foo)的简写符号。

CL-USER 19 > '#'foo
(FUNCTION FOO)

与大多数语言不同,Common Lisp并没有真正的解析器。 它有一个称为阅读器的词法分析器。 读取器使用单个字符并在表中查找它们,并调用在那里找到的函数[1]。在其他语言中,解析器所扮演的角色,在Lisp中,是由宏实现的。

[1] http://www.lispworks.com/documentation/lw51/CLHS/Body/02_b.htm

例如,分号的阅读器使用行的其余部分并将其作为注释丢弃。 因此,例如,打开括号的读取器调用递归读取列表元素的函数。 因此,例如,单引号递归地读取单个表单,然后将其括在引号中。 因此,'(1 2 3(读作(引文(1 2 3((。 这些关键的复杂令牌读取器有很多。

[2] http://www.lispworks.com/documentation/lw51/CLHS/Body/02_d.htm

字符##提供了一个放置大量额外读者行为的地方。 哈希读取器重复主读取器的设计。 它使用另一个字符并在表中查找该字符并调用在那里找到的函数。 其中有很多[3]。

[3] http://www.lispworks.com/documentation/lw51/CLHS/Body/02_dh.htm

因此,例如,

我们有一个类似于读取向量的列表的阅读器,例如#(1 2 3(。 因此,例如,我们有一个用于单个字符的阅读器,以便您可以分别输入单个分号、双引号或句点作为 #;#"#.

回答您的具体问题:引用的哈希读取器,例如#'foo,类似于常规引用的哈希读取器。 它读取以下令牌并将其包装在函数中。 #'foo 读作 (函数 foo(。

可以修改阅读器的表格以自定义语言。 表中的条目称为读取器宏。 这个名字往往会让人们感到困惑,因为它们与 defmacro 定义的宏截然不同。 它们共同提供了所谓的"发展"语言的能力[4]。

[4] http://www.catonmat.net/blog/growing-a-language-by-guy-steele/

使用 #'foo 和 'foo

作为函数指示符的另一个区别是 #'foo 计算函数对象,但 'foo 计算为符号。因此,使用 'foo 将查找函数对象的工作转移到以后的时间。如果您在循环的每个周期中执行此操作,而不是仅执行一次,则可能会对性能造成明显影响。

CL-USER> (defun foo () 42)
FOO
CL-USER> (read-from-string "'foo")
=> (QUOTE FOO), 4
CL-USER> (eval *)
FOO
CL-USER> (read-from-string "#'foo")
=> (FUNCTION FOO), 5
CL-USER> (eval *)
=> #<FUNCTION FOO>

经验告诉我,在一个由许多部分组成的大系统中,"'"与"#'"的成语使修补更容易。原因是每次遇到符号时都会查找与该符号关联的函数对象,并且在每个环境中都会查找该对象。一旦您加载了(当然是交互式的(新定义(很可能是补丁(,下次遇到它时就会使用它。性能成本确实很小,但灵活性的优势是巨大的。想象一下,当再次尝试应用程序时,客户的脸说"哇!它现在有效! :-(

相关内容

  • 没有找到相关文章

最新更新