0

I've got a Clojure file which I'm importing other library functions into with :require and :refer in the ns declaration.

I now want to eval some code in that namespace and have it call those libraries.

But when I call it, I get an "Unable to resolve symbol" for the referred function I'm trying to use.

I'm guessing I have to pass it explicitly in to the eval somehow but can't find any examples.

Second question. I'd ideally like to not use Clojure's ordinary eval at all but to run Babashka SCI. Is there a way to pass libraries from my Clojure environment into this?

Update. Code example.

(ns clj-ts.card-server
  [:require
  ...
  [patterning.layouts :refer [framed clock-rotate etc]]
  ...
  )

...


(defn one-pattern
  "Evaluate one pattern"
  [data]
  (let [pattern
        (try
          (eval (read-string data))
          (catch Exception e
            (let [sw (new java.io.StringWriter)
                  pw (new java.io.PrintWriter sw) ]
              (.printStackTrace e pw)
              (str "Exception :: " (.getMessage e) (-> sw .toString) ))) )
        ]
...
  )

Then when calling one-pattern with the following as the data argument


(let [a-round
        (fn [n lc fc]
          (clock-rotate
           n (std/poly
              0 0.5 0.4 n
              {:stroke lc
               :fill fc
               :stroke-weight 3})))
        
        ]
    (a-round 8 (p-color 140 220 180)  (p-color 190 255 200 100) )
    )

I get an error

Caused by: java.lang.RuntimeException: Unable to resolve symbol: clock-rotate in this context
interstar
  • 26,048
  • 36
  • 112
  • 180
  • Would you be able to provide an example REPL session that demonstrates the error? That would be helpful in answering your question. – dorab Feb 20 '22 at 18:19
  • Code example added – interstar Feb 20 '22 at 18:45
  • 1
    Thanks for the example code. The data argument `(let [a-round ...` that you show above, is that a string? From my reading of the code, `one-pattern` expects a string. Is the data parameter and the call to `one-pattern` happening in the `clj-ts.card-server` namespace? What I'm guessing is that the exception is being thrown because the `(let [a-round....` form is being evaluated in a namespace that does not have `clock-rotate` defined. Would you show the exact steps you are taking to cause the exception? – dorab Feb 21 '22 at 03:42
  • Yes. It's a string. And the problem is that clock-rotate isn't visible inside the eval. I'm assuming there must be a way to tell the eval that it should use the namespace that contains clock-rotate. Or to pass values like clock-rotate into it. What I don't know is how to do that. – interstar Feb 21 '22 at 13:41
  • 1
    Thank you for the clarifications. Based on those, I have updated my answer with three alternatives you could use. – dorab Feb 21 '22 at 18:14

2 Answers2

0

I am able to eval a form that has a refer'ed function. As the following code demonstrates.

user> (join " " ["foo" "bar"])
Syntax error compiling at (*cider-repl Projects/example:localhost:59334(clj)*:43:7).
Unable to resolve symbol: join in this context
user> (require '[clojure.string :refer [join]])
nil
user> (join " " ["foo" "bar"])
"foo bar"
user> (eval (read-string "(join \" \" [\"foo\" \"bar\"])"))
"foo bar"
user> 

EDIT:

eval performs the evaluation in the context of the "current" namespace that is bound to *ns*. There are three ways I can think of to address your question - with examples below. I've tried to match what I think your code structure is.

Here are the contents of the file foo.clj that defines the eval function evalit and refers a library function (in this case join from clojure.string).

(ns myorg.example.foo
  (:require [clojure.string :refer [join]]))

(defn evalit [s]
  (eval (read-string s)))

We first load this myorg.example.foo namespace:

user> (require 'myorg.example.foo)
nil

First alternative: use the fully qualified symbol for the join as follows:

user> (myorg.example.foo/evalit "(clojure.string/join \" \" [\"boo\" \"baz\"])")
"boo baz"

Second alternative: Temporarily bind *ns* to the namespace that contains the refer'ed join. Note that in this case we can just use "join" rather than the fully qualified "clojure.string/join". But we still have to use the fully qualified symbol for evalit, since we are referencing it from a different namespace.

user> (binding [*ns* (find-ns 'myorg.example.foo)]
        (myorg.example.foo/evalit "(join \" \" [\"boo\" \"baz\"])"))
"boo baz"

Third alternative: Switch to the namespace with the refer'ed function.

user> (in-ns 'myorg.example.foo)
#namespace[myorg.example.foo]
myorg.example.foo> (evalit "(join \" \" [\"boo\" \"baz\"])")
"boo baz"

Here we can use the unqualified symbols since we are in the namespace with the definition and refer.

dorab
  • 807
  • 5
  • 13
  • This isn't in the REPL. It's in a program. I've added the code. – interstar Feb 20 '22 at 18:55
  • 1
    My understanding is that if some code works in the REPL it should also work in a file. And vice versa. After all, the Clojure compiler / evaluator reads each form in the file and evals it the exact same way as the REPL. – dorab Feb 21 '22 at 03:35
  • Thanks for the update. I ended up using Babashka (see my answer). But I'll have a look into your solution too. It would be great to have this working in normal Clojure. – interstar Feb 21 '22 at 18:41
0

OK.

I ended up doing this with https://github.com/babashka/babashka SCI

In my file I basically took all the functions I had imported and put them into a new map like this

(def patterning-ns
  {'clojure.core
   {
    'clock-rotate clock-rotate
    'poly poly
    ...
   }
  }
)

Once the function names that got imported from elsewhere are in the map called patterning-ns (under the namespace name of clojure.core) then they are visible by default to any code eval-ed with

(sci/eval-string data {:namespaces patterning-ns })

Eg in

(defn one-pattern
  "Evaluate one pattern"
  [data]
  (try
    (let [
          pattern
          (sci/eval-string
           data
           {:namespaces patterning-ns })
          svg (make-svg 400 400 pattern)
          ]
       svg
       )
      )
    (catch java.lang.Exception e
      (let [sw (new java.io.StringWriter)
            pw (new java.io.PrintWriter sw) ]
        (.printStackTrace e pw)
        (println e)
        (str "Exception :: " (.getMessage e) (-> sw .toString) )) )))
interstar
  • 26,048
  • 36
  • 112
  • 180