阅读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 将查找函数对象的工作转移到以后的时间。如果您在循环的每个周期中执行此操作,而不是仅执行一次,则可能会对性能造成明显影响。
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>
经验告诉我,在一个由许多部分组成的大系统中,"'"与"#'"的成语使修补更容易。原因是每次遇到符号时都会查找与该符号关联的函数对象,并且在每个环境中都会查找该对象。一旦您加载了(当然是交互式的(新定义(很可能是补丁(,下次遇到它时就会使用它。性能成本确实很小,但灵活性的优势是巨大的。想象一下,当再次尝试应用程序时,客户的脸说"哇!它现在有效! :-(