Clojure的语法引用如何工作?

35

Clojure中的一些特殊字符是缩写

(quote (a b))'(a b)是相同的

可以通过评估来看到:

user> ''(a b)
(quote (a b))
这似乎是一种以语法为缩写的方法,我觉得这是个好主意。
但是语法引用符 ` 似乎很特殊。我想不出什么东西可以等效于它。
`(a b)

我本以为应该用类似于(syntax-quote (a b))的语法,但是它不起作用,如果我的猜想是错的,我也找不到它真正被称作什么。

我会猜测类似于(syntax-quote (a b))这样的语法,但它并不起作用。如果我猜错了,我也不知道它真正的命名。

user> '`(a b)
(clojure.core/seq (clojure.core/concat (clojure.core/list (quote user/a)) (clojure.core/list (quote user/b))))

有点神秘。

可能读者正在做一些特殊的事情,也许是因为它需要知道命名空间?

有趣的是,在语法引用中使用的特殊语法确实按照我的预期工作:

user> '~a
(clojure.core/unquote a)
user> '~@a
(clojure.core/unquote-splicing a)
user> '~'a
(clojure.core/unquote (quote a))

除了这一个之外:

user> 'a#
a#
我本来以为会产生类似于(unquote (gensym "a"))的东西。
我意识到我有点脆弱,应该去看代码。如果没有人愿意解释发生了什么或提供参考资料,那有人可以给我一个提示如何找到相关的代码以及要查找什么吗?

1
这里有一篇非常好的文章 https://blog.8thlight.com/colin-jones/2012/05/22/quoting-without-confusion.html - Sanghyun Lee
3个回答

38

我认为没有与quote函数等效的语法引用。

目前,Clojure读取器是用Java编写的。在Clojure源代码中的src/jvm/clojure/lang/LispReader.java中的SyntaxQuoteReader类可能是您想要阅读的内容。它似乎相当复杂。您可以在那里看到它构建像(seq (concat ...))这样的列表。

                ret = RT.list(SEQ, RT.cons(CONCAT, sqExpandList(seq)));

通常情况下,阅读器不会直接返回Clojure代码,而是立即在Java中执行正确的操作。例如,'[1 2 3]并不产生Clojure代码(vector 1 2 3)。也许它以某种方式可以工作,但实际上并没有这么做。阅读器只是创建并返回向量对象本身。
同样,SyntaxQuoteReader会立即在Java中进行一些魔法操作,以解析符号命名空间并自行创建gensym,并返回一些混淆和复杂的Clojure代码,以完成正确的操作,但对人类来说并不容易阅读。我不知道它是否必须这样做,或者因为在Java中这样做更容易,或者出于性能或其他原因。同样,我不知道quasiquote是否可以存在于Clojure中,而不是作为普通的宏/特殊形式不存在。我不明白为什么它不能存在。
在同一文件中的WrappingReader是处理'(普通的quote)的类。你可以看到它只是将传递给它的任何内容包装在一个列表中,其中包含符号quote和您的参数。这很简单。请注意,此类还处理@,因此'@foo确实返回(deref foo)这个线程可能会提供更多信息。

编辑

这是一个概念验证的quasiquote宏。请注意,此代码依赖于和滥用Clojure内部的一种可怕方式。请不要将其用于任何事情。

user> (defmacro quasiquote [x]
        (let [m (.getDeclaredMethod clojure.lang.LispReader$SyntaxQuoteReader 
                                    "syntaxQuote" 
                                    (into-array [Object]))]
          (.setAccessible m true)
          (.invoke m nil (into-array [x]))))
#'user/quasiquote
user> (let [x 123] `(x 'x ~x))
(user/x (quote user/x) 123)
user> (let [x 123] (quasiquote (x 'x ~x)))
(user/x (quote user/x) 123)

1
这就是深奥的黑魔法!真是一种技巧!我已经没有更多点赞给你了,但这很棒。 - John Lawrence Aspden
关于 quasiquote 宏的一件事需要注意,如果您需要支持隐式 gensym,则在调用之前添加类似 (push-thread-bindings {(get-static-field clojure.lang.LispReader 'GENSYM_ENV) {}}) 的内容。 - Nicolas Buduroi
我忘了你必须将表达式包装在try...finally中,并在finally子句中调用pop-thread-bindings以清除它。 - Nicolas Buduroi

4

hiredman已经实现了一个完全基于Clojure的版本的语法引用。虽然不是很简单,但是这是一个很好的概念证明。


3
你似乎已经很好地掌握了宏语法,因此我没有太多可以补充的。
programming clojure论坛上有一些涉及该主题的讨论。你可以随意阅读这里的代码,请查看第352行。

谢谢Arthur,这些真的很方便。 - John Lawrence Aspden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接