4

假设我有一个带有 namespace 的库 xx xx.core,我正在用纯 Clojure 编写它,打算同时针对 Clojure 和 ClojureScript。这样做的实际方法似乎是使用lein-cljsbuild 的交叉和条件注释。到现在为止还挺好。 这个前提现在已经过时了。lein-cljsbuild 已被弃用,取而代之的是reader conditionals,并且还有许多其他命名空间/宏 ClojureScript 增强功能。请参阅下面的更新答案

假设 xx 有一堆变量,我希望它的用户(无论是在 Clojure 还是 ClojureScript 中)都能够使用。这些可以分为三种类型的变量。

  • 不依赖 xx 中的宏的函数/其他变量(我将这些称为 type-1 变量)
  • 恰好发生的函数/其他变量依赖于 xx 中的宏(我将称这些类型为 2 变量

.cljs但是,由于 ClojureScript 要求宏在它们自己的特殊命名空间中与常规命名空间分开.clj,因此所有宏都必须与xx.core.

但是其中一些其他 var(type-2 var)的实现偶然地依赖于这些宏!

(可以肯定的是,似乎只有宏可以使用ClojureScriptuse-macrorequire-macro.xx/core.clj在 ClojureScript 测试文件中使用(:use-macro xx.core :only […]). 编译器然后为ClojureScript 文件引用WARNING: Use of undeclared Var的每个非宏变量发出一条消息。)xx.core

在这种情况下,人们倾向于做什么?在我看来,我唯一能做的就是……将库的公共 API 拆分为三个命名空间:一个用于类型 1 变量,一个用于宏,一个用于类型 2 变量。类似...<code>xx.core, xx.macro, and xx.util?...</p>

当然,这很糟糕,因为现在 xx 的任何用户(无论是在 Clojure 还是 ClojureScript 中)都必须知道每个 var(可能有几十个)是否在其实现中碰巧依赖于一个宏,以及它因此而依赖于哪个命名空间属于。如果我只针对 Clojure,这将是不必要的。如果我想同时针对 Clojure 和 ClojureScript,这真的是现在的情况吗?

4

2 回答 2

4

这个问题的前提在几年前基本上已经过时了。通过此更新,我正在尽自己的一份力量,不要让过时的信息污染网络。

与 Clojure 不同,ClojureScript 通常在与运行时分开的编译阶段编译宏。还有很多附带的复杂性。但是,由于多项改进,情况已大大改善。

2015 年的 1.7 版开始,Clojure 和 ClojureScript 现在支持读取器条件.cljc,这使得可以在同一个文件中为 Clojure、ClojureScript、Clojure CLR 或所有三个定义宏和函数: #?(:clj …, :cljs …, :cljr …, :default …). 仅此一项就可以缓解大部分问题。

此外,ClojureScript 本身现在已经进行了一些增强,ns从而消除了命名空间用户的许多其他附带的复杂性。它们现在记录在与 Clojure 的区别,§ 命名空间中。它们包括隐式宏加载、内联宏规范和自动别名clojure命名空间:

隐式宏加载:如果需要或使用名称空间,并且该名称空间本身需要或使用其自己的名称空间中的宏,则将隐式需要或使用相同规范的宏。此外,在这种情况下,宏变量可能包含在 :refer 或 :only 规范中。这通常会导致简化库的使用,因此使用命名空间不需要关注明确区分某些变量是函数还是宏。例如:

(ns testme.core (:require [cljs.test :as test :refer [test-var deftest]]))将导致 test/is 正确解析,以及 test-var 函数和 deftest 宏不合格。

内联宏规范:为方便起见, :require 可以指定为 :include-macros true 或 :refer-macros [syms...​]。两者都脱糖成显式加载包含宏的匹配 Clojure 文件的表单。(这与内部需要的命名空间是否需要或使用自己的宏无关。)例如:

(ns testme.core
  (:require [foo.core :as foo :refer [foo-fn] :include-macros true]
            [woz.core :as woz :refer [woz-fn] :refer-macros [apple jax]]))

是糖

(ns testme.core
  (:require [foo.core :as foo :refer [foo-fn]]
            [woz.core :as woz :refer [woz-fn]])
  (:require-macros [foo.core :as foo]
                   [woz.core :as woz :refer [apple jax]]))

自动别名clojure命名空间:如果clojure.*需要或使用不存在的命名空间,并且存在匹配的 cljs.* 命名空间,则将加载命名空间并从命名空间到命名空间cljs.*自动建立别名。例如:clojure.*cljs.*

(ns testme.core (:require [clojure.test]))

会自动转换为

(ns testme.core (:require [cljs.test :as clojure.test]))`

最后,ClojureScript 现在有了第二个目标:引导的、自托管的 ClojureScript,也就是 CLJS-in-CLJS。与 CLJS-on-JVM 编译器相比,引导的 ClojureScript 编译器实际上可以编译宏!分离仍然在源文件中强制执行,但它的 REPL 可以将它们与函数混合运行。

Mike Fikes 就 Clojure–ClojureScript 可移植性的这些和其他问题撰写了一系列有价值的文章,同时这些功能正在开发中。这些包括:

即使在 2017 年,看到 ClojureScript 继续成熟也是令人兴奋的。

于 2017-10-28T04:46:47.623 回答
3

看来您正确理解了情况:)

关于:“xx 的任何用户(无论是在 Clojure 还是 ClojureScript 中)都必须知道每个 var(可能有几十个)是否在其实现中碰巧依赖于一个宏,以及它因此属于哪个命名空间。”

您可以再添加两个命名空间,api.clj 和 api.cljs,其中包括该 api 的每个 var 的正确命名空间,并从该决定中消除一些痛苦。不过,这个领域似乎仍然很新。

于 2012-12-12T19:03:44.193 回答