使用#a.k.a. read-macro

时间:2014-06-01 07:20:51

标签: lisp common-lisp reader-macro

阅读书"让Over Lambda"作者:Doug Hoyte,我发现了#.符号的以下描述,a.k.a. read-macro:

  

使用COMMON LISP内置的基本读取宏是#。读取时间eval宏。此读取宏允许您将对象嵌入到您无法序列化的表单中,但可以使用一些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 个答案:

答案 0 :(得分: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)

答案 1 :(得分:7)

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

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

例如,分号的阅读器会占用该行的其余部分,并将其作为注释丢弃。所以,例如,读者开放paren。调用递归读取列表元素的函数。因此,例如单引号递归读取单个表单然后将其包装在引号中。因此'(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读作(function foo)。

可以修改阅读器表格以自定义语言。表中的条目称为读取器宏。这个名称往往会使人们感到困惑,因为它们与defmacro定义的宏完全不同。它们共同提供了被称为“成长”的能力。语言[4]。

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

答案 2 :(得分: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>

答案 3 :(得分:0)

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