【问题标题】:Is it possible to turn off qualification of symbols when using clojure syntax quote in a macro?在宏中使用 clojure 语法引用时是否可以关闭符号限定?
【发布时间】:2015-05-07 14:24:11
【问题描述】:

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

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

我的“宏”如下所示:

(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”(我仍然需要努力连接到名称的词干,但我并不担心)。

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

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

在我看来,语法引用比标准引用更“有能力”。这是真的?或者,您能否通过诡计,始终使标准引号的行为与语法引号相同?如果您无法使用语法引号关闭限定条件,我如何才能使用标准引号?尝试将其作为 defmacro 执行此操作是否会有所收获?

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

【问题讨论】:

    标签: emacs clojure elisp


    【解决方案1】:

    使用语法引用时,无法“关闭”符号的限定。但是,您可以这样做:

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

    这无疑是相当乏味的。没有语法引号的等价物是:

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

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

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

    【讨论】:

      【解决方案2】:

      根据 justncon 的输入,这是我的最终解决方案。我必须做一些额外的格式化才能使函数名称上的字符串 concat 正确,但一切都非常像他推荐的那样:

      (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" ))
      
      1. 宏扩展非常精细,需要大量练习。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-30
        • 1970-01-01
        • 2021-04-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多