2

阅读 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

以同样的成功。

4

4 回答 4

7

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

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)
于 2014-06-01T07:46:36.857 回答
7

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

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

例如,分号的阅读器会消耗该行的其余部分并将其作为注释丢弃。因此,例如,开放括号的阅读器。调用递归读取列表元素的函数。因此,例如,单引号递归地读取单个表单,然后将其包装在引号中。因此 '(1 2 3) 被读作 (quote (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/growth-a-language-by-guy-steele/

于 2014-06-01T13:10:51.840 回答
3

使用 #'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>
于 2014-06-01T20:55:23.470 回答
0

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

于 2017-11-17T17:10:06.167 回答