Need help to resolve symbols programmatically

@joinr, I was starting to wonder the same thing myself.

Part of this exercise has been just a learning process for me to understand the clojure reader/resolver/backquote mechanism. I know enough to come up with a consistent solution. Although it may not be culturally compatible with clojure.

I don’t think culture matters if you can solve your problem or a client’s problem and get paid. I think there are probably many examples of similar rule systems (to include type inferencers like core.typed) that do similar things. Maybe a broader survey of extant systems would be enlightening. OTOH, if you can mock up a solution now that works for your cases, and isn’t onerous on the user, I would consider it a success.

Just because I’m not sure if it got lost in all the other ideas. I think this one might be a good easy one, in fact should have thought of it first.

If you capture the value of *ns* when provided the namespace. And then when you resolve the DSL, you can pass ns-resolve the captured namespace. That will re-create the context of the user namespace. Though this assumes the user namespace exists on the classpath when you’re resolving.

Something like:

(ns your.lib
  (:require [clojure.walk :as walk]))

(def dsl (atom nil))

(defn define
  [local-dsl]
  (reset! dsl {:namespace *ns*
               :dsl local-dsl}))

(defn process
  []
  (walk/postwalk
   (fn [e]
     (if (and (symbol? e)
              ;; Here you'd put all your reserved DSL symbols
              (not (#{'or 'and 'not 'satisfies} e)))
       (ns-resolve (:namespace @dsl) e)
       e))
   (:dsl @dsl)))
(ns user
  (:require [your.lib :as yl]))

(defn ten?
  [e]
  (if (= 10 e) true false))

(yl/define
  (yl/define
    '(or (satisfies ten?) 
      (and Integer (not (satisfies odd?))))))
(ns somewhere-else
  (:require [your.lib :as yl]))

(yl/process)
;; => {:namespace #object[clojure.lang.Namespace 0xe0d1dc4 "user"]
;; => :dsl (or (satisfies #'user/ten?) (and java.lang.Integer (not (satisfies #'clojure.core/odd?))))}

Now as you see, with this, you can capture the DSL from one namespace, and within another, you get back the DSL you want with every symbol resolved within the context of the original namespace pointing to the correct thing, with your DSL reserved symbols intact:

(or (satisfies #'user/ten?) (and java.lang.Integer (not (satisfies #'clojure.core/odd?))))

I think this is the behavior you originally wanted, seems like a no compromise solution, though you probably will need to refactor your lib to keep track of all the originating namespaces along the DSLs.

I think my prior answer might make that clear. You don’t need a define or a process, I just use those as mocks for whatever your library provides. I’ve assumed the user gives your library a quoted list at some point, so I’m using define as whatever that function in your lib would be where this happens. And I assume you process that quoted list in some other function in your library, so I’ve used process as the stub for whatever that would be in yours. So they’re just for demonstrative purpose.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.