在宏中使用 clojure 语法引号时是否可以关闭符号限定?

Is it possible to turn off qualification of symbols when using clojure syntax quote in a macro?

我正在从 clojure 函数生成 emacs elisp 代码。我最初开始使用 defmacro,但我意识到,因为我要跨平台并且必须手动将代码评估到 elisp 环境中,所以我可以轻松地使用标准 clojure 函数。但基本上我所做的是非常宏观的。

我这样做是因为我的目标是创建一个 DSL,我将从中以 elisp、clojure/java、clojurescript/javascript 甚至 haskell.[=19 生成代码=]

我的 "macro" 如下所示:

(defn vt-fun-3 []
  (let [hlq "vt"]
    (let [
         f0 'list
         f1 '(quote (defun vt-inc (n) (+ n 1)))
         f2 '(quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8))))]
      `(~f0 ~f1 ~f2)
      )))

这会生成一个包含两个函数定义的列表 -- 生成的 elisp defun 和一个单元测试:

(list (quote (defun vt-inc (n) (+ n 1))) (quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8)))))

然后从一个 emacs 暂存缓冲区,我利用 clomacs https://github.com/clojure-emacs/clomacs 导入到 elisp 环境中:

(clomacs-defun vt-fun-3 casc-gen.core/vt-fun-3)
(progn
    (eval (nth 0  (eval  (read (vt-fun-3)))))
    (eval (nth 1  (eval  (read (vt-fun-3))))))

从这里我可以运行函数和单元测试:

(vt-inc 4)
--> 5
(ert "vt-inc-test")
--> t

注意:与所有宏一样,语法引用和转义非常脆弱。我花了一段时间才弄清楚在 elisp 中正确评估它的正确方法(整个“(quote(list..)”前缀)。

无论如何,正如第一个 "let" 上存在的 "hlq"(高级限定符)所暗示的,我想在任何生成的符号前加上这个 hlq 而不是硬编码它。

不幸的是,当我在 "f1" 上使用标准引号和转义符时:

 f1 '(quote (defun ~hlq -inc (n) (+ n 1)))

这会生成:

    (list (quote (defun (clojure.core/unquote hlq) -inc (n) (+ n 1))) 
(quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8)))))

换句话说,它将 'clojure.core/unquote' 替换为“~”,这不是我想要的。

clojure 语法反引号:

f1 `(quote (defun ~hlq -inc (n) (+ n 1)))

没有这个问题:

(list (quote (casc-gen.core/defun vt casc-gen.core/-inc (casc-gen.core/n) (clojure.core/+ casc-gen.core/n 1))) (quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8)))))

它可以根据需要正确地转义和插入 "vt"(我仍然需要努力连接到名称的词干,但我并不担心)。

问题解决了吧?不幸的是,语法 quote 完全限定了所有符号,这是我不想要的,因为代码将在 elisp 下 运行ning。

有没有办法在使用语法引号(反引号)时关闭符号限定?

在我看来,语法引述比标准引述 "capable" 多。这是真的?或者,您能否通过诡计使标准引用的行为始终与语法引用相同?如果你不能关闭语法引用的限定,我怎么能用标准引用让它工作呢?我会通过尝试将此作为 defmacro 来获得任何好处吗?

最坏的情况是我必须 运行 在生成的 elisp 上使用正则表达式并手动删除任何限定条件。

使用语法引号时无法"turn off"限定符号。但是您可以这样做:

(let [hlq 'vt] `(~'quote (~'defun ~hlq ~'-inc (~'n) (~'+ ~'n 1))))

这确实很乏味。没有语法引号的等价物是:

(let [hlq 'vt] (list 'quote (list 'defun hlq '-inc '(n) '(+ n 1))))

然而,当使用标准 quote 为整个表单添加前缀时,无法获得所需的输出。

关于改用defmacro的问题,据我了解你的意图,我认为你使用宏不会有任何好处。

根据 justncon 的意见,这是我的最终解决方案。我不得不做一些额外的格式化以使函数名称上的字符串连接正确,但一切都非常像他推荐的那样:

(defn vt-gen-4 []
  (let [hlq 'vt]
   (let [
         f1 `(~'quote (~'defun ~(symbol (str hlq "-inc")) (~'n) (~'+ ~'n 1)))
         f2 `(~'quote (~'defun ~(symbol (str hlq "-inc-test")) () (~'should (~'= (~(symbol (str hlq "-inc")) 7) 8))))
         ]
     `(~'list ~f1 ~f2))))

我学到了什么:

  1. 语法引用是要走的路,你只需要知道如何在基本级别控制取消引用。

  2. ~'(波浪线)是我的朋友。在语法引用表达式中,如果您在函数或 var 之前指定 ~',它将按指定传递给调用者。

取表达式 (+ 1 1)

以下是此表达式如何在基于各种转义级别的语法引用表达式中扩展的概要:

(defn vt-foo []
  (println "(+ 1 1) -> " `(+ 1 1))      -->  (clojure.core/+ 1 1)
  (println "~(+ 1 1) -> " `~(+ 1 1))    -->  2
  (println "~'(+ 1 1) -> " `~'(+ 1 1))  --> (+ 1 1)
  )

最后一行是我想要的。第一行是我得到的。

  1. 如果您转义一个函数,那么不要转义您想要转义的任何参数。例如这里我们要 在宏扩展时调用 "str" 函数并将变量 "hlq" 扩展为其值 'vt:

    ;; this works
    f1 `(quote (defun ~(str  hlq "-inc") ~hlq (n) (+ n 1)))

    ;; doesn't work if you escape the hlq:
    f1 `(quote (defun ~(str  ~hlq "-inc") ~hlq (n) (+ n 1)))

我想逃脱涵盖了您逃脱的单位中的所有内容。通常你会转义原子(如字符串或符号),但如果它是一个列表,那么列表中的所有内容也会自动转义,所以不要双重转义。

4) FWIW,在得到最终答案之前,我结束了编写正则表达式解决方案。绝对不是那么好:

(defn vt-gen-3 []
  (let [hlq "vt"]
    (let
        [
         f0 'list
         f1 `(quote (defun ~(symbol (str  hlq "-inc")) (n) (+ n 1)))
         f2 '(quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8))))
         ]
      `(~f0 ~f1 ~f2)
      ))
  )

;; this strips out any qualifiers like "casc-gen.core/"
(defn vt-gen-3-regex []
  (clojure.string/replace (str (vt-gen-3)) #"([\( ])([a-zA-Z0-9-\.]+\/)" "" ))
  1. 宏展开很微妙,需要多多练习