我可以将 Clojure 表单从一个包转换为另一个包吗?

Posted

技术标签:

【中文标题】我可以将 Clojure 表单从一个包转换为另一个包吗?【英文标题】:Can I convert a Clojure form from one package to another? 【发布时间】:2013-03-08 12:11:04 【问题描述】:

背景

我为 Emacs 编写了一个 hack,让我可以将 Clojure 表单从编辑器缓冲区发送到 REPL 缓冲区。它工作正常,除了如果两个缓冲区位于不同的命名空间中,复制的文本通常没有意义,或者更糟糕的是,它可能有意义但与编辑器缓冲区中的含义不同。

我想转换文本,使其在 REPL 缓冲区中有意义。

Common Lisp 中的一种解决方案

在 Common Lisp 中,我可以使用以下函数来做到这一点:

;; Common Lisp

(defun translate-text-between-packages (text from-package to-package)
  (let* ((*package* from-package)
         (form (read-from-string text))
         (*package* to-package))
    (with-output-to-string (*standard-output*)
                           (pprint form))))

还有一个示例使用:

;; Common Lisp

(make-package 'editor-package)
(make-package 'repl-package)

(defvar repl-package::a)

(translate-text-between-packages "(+ repl-package::a b)"
                                 (find-package 'editor-package)
                                 (find-package 'repl-package))
;; => "(+ A EDITOR-PACKAGE::B)"

输入字符串和输出字符串中的包名限定是不同的——这正是解决包之间复制和粘贴文本问题所需要的。

(顺便说一句,有一些关于如何在 Common Lisp 进程中运行翻译代码以及如何在 Emacs 世界和 Common Lisp 世界之间移动内容的内容,但我对此表示满意,我并不想特别深入在这里。)

Clojure 中的非解决方案

这是 Clojure 的直接翻译:

;; Clojure

(defn translate-text-between-namespaces [text from-ns to-ns]
  (let [*ns* from-ns
        form (read-string text)
        *ns* to-ns]
    (with-out-str
      (clojure.pprint/pprint form))))

还有一个示例使用:

;; Clojure

(create-ns 'editor-ns)
(create-ns 'repl-ns)

(translate-text-between-namespaces "(+ repl-ns/a b)"
                                   (find-ns 'editor-ns)
                                   (find-ns 'repl-ns))
;; => "(+ repl-ns/a b)"

所以 Clojure 中的翻译功能什么也没做。这是因为 Common Lisp 和 Clojure 中的符号和包/命名空间的工作方式不同。

在 Common Lisp 中,符号属于一个包,并且在读取时确定一个符号的包。

在 Clojure 中,有充分的理由,符号不属于命名空间,并且符号命名空间的确定发生在评估时。

这可以在 Clojure 中完成吗?

最后,我的问题是:我可以将 Clojure 代码从一个命名空间转换到另一个命名空间吗?

【问题讨论】:

你看过github.com/clojure/tools.reader吗? @sw1nn:我刚刚快速浏览了一下。我认为那里没有任何帮助 - 没有关于名称空间的内容。我认为这是关于符号解析,它发生在评估时,而不是读取时。 我很困惑。为什么不在适当的位置评估表单 - 像 nrepl 这样的工具将在正确的名称空间中执行此操作,因此一切正常。你想达到什么结果?如果你想要一个交互式的 repl,那么将当前的 REPL 移动到当前缓冲区的命名空间也很容易。 @PhilLord 在演示代码或进行演示时,将代码发送到 REPL 缓冲区通常会很好。如果我忘记将 REPL 设置为具有正确的命名空间,我希望发生一些明智的事情。现在,如果 REPL 和当前缓冲区有不同的命名空间,我会在向 REPL 发送文本之前要求确认。这已经足够好了,但它让我想到了我在这里提出的问题。 【参考方案1】:

我不了解您的用例,但这是一种将符号从一个命名空间转换为另一个命名空间的方法。

(require 'clojure.walk 'clojure.pprint)

(defn ns-trans-form [ns1 ns2 form]
  (clojure.walk/prewalk
    (fn [f] (if ((every-pred symbol? #(= (namespace %) ns1)) f)
                (symbol ns2 (name f))
                f))
    form))

(defn ns-trans-text [ns1 ns2 text]
    (with-out-str
      (->> text 
           read-string 
           (ns-trans-form ns1 ns2) 
           clojure.pprint/pprint)))

(print (ns-trans-text "editor-ns" "repl-ns" "(+ editor-ns/a b)" ))
;=> (+ repl-ns/a b)

所以,editor-ns/a 被转换为 repl-ns/a

【讨论】:

【参考方案2】:

(回答我自己的问题...)

鉴于从命名空间外部引用命名空间的非公共变量并不容易,因此没有简单的方法可以做到这一点。

根据http://christophermaier.name/blog/2011/04/30/not-so-private-clojure-functions 的想法,也许黑客是可能的。这将涉及遍历表单并创建新符号,这些符号解析为与原始表单中引用的变量具有相同值的新变量。也许我会在某个时候进一步调查,但不是现在。

【讨论】:

我决定写一篇关于这一切的博客文章,最后写了一些关于 Clojure 和 Common Lisp 中符号之间差异的一般性讨论。这是here。

以上是关于我可以将 Clojure 表单从一个包转换为另一个包吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何将Java类转换为另一个类

将对象列表转换为另一个对象

如何将对象从一个 JpaRepository 转换为另一个 JpaRepository

Livewire 组件转换为另一个 livewire 组件

将反应从一个系统转换为另一个系统

无法将日期时间从字符串转换为另一种格式(例如:14/12/2021 03:34:03 PM 到 03:34 pm)