Might have found an interesting diff in sci
from clj to cljs…
(defn eval [form]
(sci/eval-form (sci/fork es/context) form))
(is (= o/identity-operator
(eval
'(do (require '[sicmutils.operator :as o])
o/identity-operator)))
"can sci internally require namespaces?")
@borkdude I had this test -
in clj
, that that passes; in cljs
it fails, since the eval form returns a var, not a resolved var
@sritchie09 I'm having dinner right now, but feel free to post an issue with the desired outcome
sg!
(whoops, this was from an error of mine 🙂 I was fiddling around and accidentally removed my own deref on the value)
ah, okay, this is more the error I was anticipating with eval:
(let [m {:bindings {'make (fn [form] (cljs.core/eval form))}}]
(sci/eval-form (sci/init m) '(make `(fn [x#] (* x# 12)))))
@sritchie09 What is the error?
Execution error (Error) at (<cljs repl>:1).
cljs.core/*eval* not bound
another discovery (as you hint at and document in the README): if I want to use dynamic variables to toggle behavior (like certain simplifier paths, or a compile mode), then instead of a Clojure dynamic variable:
(def ^:dynamic *mode* :sci)
I can use
(def mode
(sci/new-dynamic-var '*mode* :sci))
which will work both in and out of the SCI environmentyes, that will work in both envs, but in the outside env you need to deref explicitly
cljs.core/*eval* not bound
this is a CLJS problem, not a sci problemyou care calling cljs.core/eval here, not something inside sci
that is bound in self-hosted cljs only
yeah, that’s right. can I define a function outside of sci that, when invoked inside of sci, will call sci’s eval?
@sritchie09 you can just call eval
inside sci
oh… I guess I can bind the actual “eval” to the dynamic variable
bind it inside sci
that's not needed, it already works inside sci without config
@borkdude the problem is I don’t want the user to do that, this is a few levels deeper than what the user would interact with
they are calling (compile-fn f)
so you want to forbid eval
?
you can either do this through :deny
or just override clojure.core/eval
with something else
no, sorry, I want that to be fine and possible - but I want the user to be able to toggle a compilation mode inside SCI, either “sci” or “eval” mode
or, what I actually wanted to do was make ‘eval’ the default inside of sci
let me link
https://github.com/sicmutils/sicmutils/blob/master/src/sicmutils/expression/compile.cljc#L546-L552
`*mode*` is dynamic here
https://github.com/sicmutils/sicmutils/blob/master/src/sicmutils/expression/compile.cljc#L516-L523
and then compile-native
calls eval
internally
but compile-native
is defined outside of sci
so it captures cljs.core/eval
… what would be excellent is if I could write this in some way that eval
resolved to the sci eval
inside of sci
ok, so you want eval to be bound to either sci eval or clojure eval - right?
depending on the mode
https://github.com/sicmutils/sicmutils/blob/master/src/sicmutils/expression/compile.cljc#L516-L532
this latter snippet has the code for both “modes”
“sci mode” is a little different from what you just stated (sorry I’m being confusing here!) “sci mode” means, “use sci/eval-form
“. “native mode” means, “call eval
”.
the reason this distinction matters is that for sci/eval-form
, I provide a context with function substitutions - #(Math/sin %)
for sin
, etc -
and with eval
, I have to do the postwalk-replace manually before calling eval
what you can do is (sci/eval-string ctx "eval")
, this will get you the eval function inside sci
but the way you implemented it here is pretty much the same I think
this is what I had meant yesterday about “calling sci from inside sci”… I see now that I am technically not calling sci INSIDE sci, I am bringing in some external fn that, externally, used SCI
so probably I should just not worry, since the behavior will be identical. But what I HAVE learned here is that I should be defining any dynamic variables the SCI Way, if I want them usable outside and inside SCI.
that's true, sci by design doesn't want to have anything to do with Clojure vars
I wonder if, as a style thing, I should just NOT import any dynamic variable now, so they’re not even available in SCI-mode of sicmutils
since they’re not useful if they can’t be rebound (that is until I convert them in the way we’ve described)
Clojure itself has dynamic vars that aren't usually re-bound by the user, if that's what you mean?
e.g. *1
etc are usually only set by tooling, not by the user
oh, interesting, let me check if I can do that
so that's ok, I guess
(defn rebound [f]
(binding [*mode* :testing]
(f)))
if you want them to be re-bound by the user, you will have to invoke (sci/eval-form ...)
within a (sci/binding [the-sci-dyn-var 12] ...)
ahh, I think I see your problem. you want a dynvar that can be re-bound in both clojure and sci expressions?
yeah, I want the user to, at a min, be able to look at the var and see its binding
if I DON’T resolve the dynamic var when I declare the sci context this works great
(eval
'(do (require '[sicmutils.expression.compile :as c])
(c/rebound (fn [] @c/*mode*))))
that can be accomplished by exposing the implementation detail using a function
yup, that is a better design
what I sometimes also do is have both a clojure and sci var and synchronize them "just in time" in the places where it matters
e.g. if I want to include Clojure functions that depend on *out*
, I include them in sci as:
(fn [arg] (binding [*out* @sci/out] (clojure-function arg))
(defn sci-ns
"Given a map of symbol => var, returns a map of symbol => var with:
- any pair removed whose value is a macro (tagged with `:macro true` metadata)
- all other values resolved"
[sym->var]
(letfn [(process [[sym var]]
(cond
;; Inside SCI, macros are replaced by rewritten-as-functions
;; versions of themselves, with additional slots for `&form` and
;; `&env`.
(macro? var)
(if-let [sci-macro (macros/all sym)]
[[sym sci-macro]]
[])
;; Keep dynamic variables as unresolved vars, so that they can
;; at least be inspected (at which point they'll reveal any
;; rebindings applied by the system)
(dynamic? var) [[sym var]]
;; by default, the SCI environment holds values, not the vars
;; that they were attached to in non-SCI land.
:else [[sym @var]]))]
(into {} (mapcat process) sym->var)))
this was my final thing. {'sicmutils.env (sci-ns (ns-bindings 'sicmutils.env))}
is a good value for :namespaces
so (dynamic? var) [[sym var]]
includes a normal Clojure var inside the sci namespaces?
yup
which will then change its binding, if an external thing rebinds it
ie deref-ing it inside sci reflects changes from functions that do stuff like this:
(defn rebound [f]
(binding [*mode* :testing]
(f)))
is that a no-no?
The reason sci doesn't want to have anything to do with Clojure vars, is that creating vars pollutes the global runtime. I'm not sure if sci works properly when you include Clojure vars. Function calls work, because vars implement IFn, but they work differently when just using them as values.
But if it works, it works. I've never tried this
this is for inspection-only, and still a little weird (since it’s a thing you have to deref to inspect)…
the better solution is to expose any modifications to dynamic vars via fns
Functions are nice, but if you do want to use dynvar, this is how I would do it:
$ clj
Clojure 1.10.1
user=> (def ^:dynamic *dyn-var* 12)
#'user/*dyn-var*
user=> (binding [*dyn-var* 13] *dyn-var*)
13
user=> (def sci-dyn-var (sci/copy-var *dyn-var* (sci/create-ns 'user)))
#'user/sci-dyn-var
user=> (sci/binding [sci-dyn-var *dyn-var*] (sci/eval-string "*dyn-var*" {:bindings {'*dyn-var* sci-dyn-var}}))
12
user=> (sci/binding [sci-dyn-var *dyn-var*] (sci/eval-string "(binding [*dyn-var* 13] *dyn-var*)" {:bindings {'*dyn-var* sci-dyn-var}}))
13
the downside of this is that *dyn-var*
and sci-dyn-var
need manual syncing
if you need to for example read the var in a function that is used both in and out sci, you will probably re-define that function using another (clojure.core/binding [])
around it in the sci config, to sync back the value set by the user