@borkdude Following up on the sandbox discussion. You mentioned re-definitions of vars do not propagate Maybe I found a bug then or i misunderstand what you mean by that ๐งต
(require '[sci.core :as sci])
(def orig-ctx (sci/init {}))
(sci/eval-string* orig-ctx "(defn hello [] :ok) (hello)") ;=> :ok
(def forked-ctx (sci/fork orig-ctx))
(sci/eval-string* forked-ctx "(defn hello [] :bad) (hello)") ;=> :bad
;; Redefinition of `hello` now also in `orig-ctx`
(sci/eval-string* orig-ctx "(hello)") ;=> :bad
I would expect/like if sci/fork would isolate the var changes only to forked-ctxIt also works the other way:
(sci/eval-string* orig-ctx "(def hello (constantly :random))")
(sci/eval-string* forked-ctx "(hello)") ;=> :random
For new vars the isolation works
(sci/eval-string* orig-ctx "(def b 1)")
(sci/eval-string* forked-ctx "b") ;=> clojure.lang.ExceptionInfo: Could not resolve symbol: b In the original context you can make the var immutable by adding :sci/built-in metadata. Then in the forked context the user cannot redefine it.
Or you can just add the hello is a direct function to prevent re-definition.
in more dynamic context that is difficult I think. I'm curious why this is the default and not by default the immutable version
the reason is that vars like in Clojure are just mutable objects. I didn't change this default. If you share mutable objects, you can mutate them, unless you don't expose them as mutable.
I clarified that in the previous discussion as well
maybe we should document that caveat though, but it's unlikely that this will change
perhaps sci/fork can have an option to copy all the vars into new objects, which is more expensive
Yeah probably I missed that and it makes sense of course that you followed the clojure path. I was thinking though if it would be possible to forward all changes to the ctx so that a fork is like fork of a clojure map. But maybe that is not desirable for other reasons
> perhaps sci/fork can have an option to copy all the vars into new objects, which is more expensive yeah that's probably what I can do myself as well, but that would be less efficient
I made clojure core etc immutable by default. you can only change them with sci/enable-unrestricted-access! (which turns off sandboxing basically, which isn't a good option for nested SCI usages, so I'll have to revisit this)
e.g. in babashka, when you use SCI, you can alter clojure.core as well, because enable-unrestricted-access is enabled globally. I'll have to change this and make it part of the context instead I think
I would like to learn more about how the vars are implemented to understand the tradeoffs better. Do you have some pointers of which namespaces in Sci I should look at?
Vars are implemented in the same way as JVM Clojure basically
look for deftype Var for example
Ah yeah thanks I will study it a bit
I pushed some (JVM only) pseudo-code here to fork vars as well in sci/fork: https://github.com/babashka/sci/commit/4b423a347c251b41c841d996c5206c1c9c68ec15
it will even clone clojure.core
takes about 2ms:
user=> (time (sci/fork ctx1 {:clone-vars true}))
"Elapsed time: 2.322375 msecs"user=> (time (dotimes [i 10000] (sci/fork ctx1 {:clone-vars true})))
"Elapsed time: 259.077834 msecs"
nilNice! That's fast enough I think
0,02ms actually. yes pretty fast
I'm getting also getting good results at 0.1ms, but with a much bigger context (and maybe a slower machine, Mac M2)
btw I'm not sure if cloning clojure.core is a good idea since vars like sci/out don't line up anymore which probably messes up printing etc
Ah yeah, so some extra filtering would be needed to get it right for every context
oh it does work:
user=> (sci/binding [sci/out *out*] (sci/eval-string* (sci/fork (sci/init {}) {:clone-vars true}) "(prn :dude)"))
"dude" I guess it works since prn is cloned but still uses the old *out*
yes, this is a problem:
user=> (sci/binding [sci/out *out*] (sci/eval-string* (sci/fork (sci/init {}) {:clone-vars true}) "*out*"))
#object[sci.impl.vars.SciUnbound 0x259f0fdb "Unbound: #'clojure.core/*out*"]makes sense. Just like the cloning of a var with an atom would still be propagated
ok so needs some extra experimentation at least
I understand the var problem a bit better now
my mental model was simplifying it a bit ๐