opyate
opyate

Reputation: 5428

Clojurescript namespace as argument

Say I have the following Clojurescript code:

(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)

How can I achieve this with Clojurescript?

one and two has the same "interface".

PS I know I can pass a function as an argument, but that doesn't answer the question. (e.g. the namespace might have many functions, and I don't want to pass them all)

 Tried 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

(Yes, there's no trace after java.lang.NullPointerException:, and I go on to show the initial namespaces resolve in the REPL session,)

If I move away from the contrived example, and try this in my Clojurescript project, I get this trace:

#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$3 (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$2 (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)

Upvotes: 0

Views: 419

Answers (1)

Olim Saidov
Olim Saidov

Reputation: 2844

You can use ns-resolve function to find a var in a namespace.

(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

But for this kind of polymorphic behavior, using protocols or multi-methods is more suitable.

UPDATE

The above code works only in Clojure, because ns-resolve does not exists in ClojureScript. In fact, ClojureScript does not have Vars.

But we can manually get the function from namespace object. We also need to mark functions with export metadata flag to prevent function names get "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)

Upvotes: 3

Related Questions