Clojurescript 命名空间作为参数

Clojurescript namespace as argument

假设我有以下 Clojurescript 代码:

(ns one)
(defn foo [] 1)

(ns two)
(defn foo [] 2)

(ns other)
(defn thing [the-ns] (the-ns/foo))

; now I want to see 1
(other/thing one)
; now I want to see 2
(other/thing two)

如何使用 Clojurescript 实现此目的?

onetwo 具有相同的“接口”。

PS 我知道我可以将函数作为参数传递,但这并不能回答问题。 (例如命名空间可能有很多功能,我不想全部传递)

尝试过 ns-resolve

boot.user=> (ns one)
nil
one=> (defn foo [] 1)
#'one/foo
one=> (ns two)
nil
two=> (defn foo [] 2)
#'two/foo
two=> (ns other (:require [cljs.analyzer.api :as api]))
nil
other=> (defn thing [the-ns] (let [f (api/ns-resolve the-ns 'foo)] (f)))
#'other/thing
other=> (other/thing 'one)

java.lang.NullPointerException:
other=> (one/foo)
1
other=> (two/foo)
2

(是的,在 java.lang.NullPointerException: 之后没有踪迹,我继续展示在 REPL 会话中解析的初始命名空间,)

如果我离开人为的例子,并在我的 Clojurescript 项目中尝试这个,我得到这个跟踪:

#object[Error Error: No protocol method IDeref.-deref defined for type null: ]
Error: No protocol method IDeref.-deref defined for type null:
    at Object.cljs$core$missing_protocol [as missing_protocol] (http://0.0.0.0:8000/index.html.out/cljs/core.js:311:9)
    at Object.cljs$core$_deref [as _deref] (http://0.0.0.0:8000/index.html.out/cljs/core.js:2164:17)
    at cljs$core$deref (http://0.0.0.0:8000/index.html.out/cljs/core.js:4945:18)
    at Function.cljs.analyzer.api.ns_resolve.cljs$core$IFn$_invoke$arity (http://0.0.0.0:8000/index.html.out/cljs/analyzer/api.js:346:51)
    at cljs$analyzer$api$ns_resolve (http://0.0.0.0:8000/index.html.out/cljs/analyzer/api.js:322:37)
    at Function.cljs.analyzer.api.ns_resolve.cljs$core$IFn$_invoke$arity (http://0.0.0.0:8000/index.html.out/cljs/analyzer/api.js:332:37)
    at cljs$analyzer$api$ns_resolve (http://0.0.0.0:8000/index.html.out/cljs/analyzer/api.js:318:37)
    at eval (eval at <anonymous> (http://0.0.0.0:8000/index.html.out/weasel/repl.js:30:495), <anonymous>:1:108)
    at eval (eval at <anonymous> (http://0.0.0.0:8000/index.html.out/weasel/repl.js:30:495), <anonymous>:9:3)
    at eval (eval at <anonymous> (http://0.0.0.0:8000/index.html.out/weasel/repl.js:30:495), <anonymous>:14:4)

您可以使用 ns-resolve 函数在命名空间中查找变量。

(ns one)
(defn foo [] 1)

(ns two)
(defn foo [] 2)

(ns other)
(defn thing [the-ns] 
  (let [f (ns-resolve the-ns 'foo)]
    (f)))


(demo.other/thing 'one) ;; returns 1
(demo.other/thing 'two) ;; returns 2

但对于这种多态行为,使用协议或多方法更合适。

更新

以上代码只能在Clojure中运行,因为ns-resolve在ClojureScript中不存在。其实ClojureScript是没有Vars的。

但我们可以手动从命名空间对象中获取函数。我们还需要使用导出元数据标志来标记函数,以防止函数名称获取 "munged":

(ns demo.one)
(defn ^:export foo [] 1)

(ns demo.two)
(defn ^:export foo [] 2)

(ns demo.other)
(defn thing [the-ns] 
  (let [f (aget the-ns "foo")]
    (f)))

(other/thing demo.one)
(other/thing demo.two)