With echo '(defrecord Example [] clojure.lang.IDeref (deref [this]))' | bb
I get java.lang.ClassCastException: java.lang.Class cannot be cast to java.util.concurrent.Future
. Not sure what's happening :thinking_face:
@djblue There are two problems here: - bb doesn't execute programs from stdin, it rather reads EDN, etc from stdin - sci doesn't support implementing interfaces (yet), only protocols
How hard would supporting interfaces be?
protocols in sci are just syntactic sugar over multi-methods, so there isn't really an interface class when you define a protocol. the problem with interfaces is that in normal Clojure it creates a real Java class which implements that interface, but in GraalVM you can't create new classes at runtime
as a workaround I'm considering something like pre-defined recipes to reify a selection of classes, but I haven't spent a lot of time on that yet. it's more or less a sci research topic
e.g. mapping a function like:
(defn foo [opts] (reify IDeref ....)
probably works, so then we have a function at compile time that can create an IDeref given some opts. But how do we get a function that can reify more than one interface?
I wonder if maybe we can proxy the interfaces via protocols?
hmm, interesting
I think for IDeref that would work, if you call deref yourself in sci, since we can patch deref to go through the "protocol" instead of the interface, but if you pass that object to other functions that use clojure's deref, that it will break
that's also the problem with implementing Java interfaces: we can fake things in sci, but as soon as you pass the object to some other Java class, that class doesn't know we are faking things
Thanks for all the context, I'll keeping thinking about this problem.
Thanks, yeah, it's really one of the more challenging problems in sci. Since protocols are only used from Clojure they are more manageable, although that was also a bit of a head-scratcher :)
Protocols can now even be implemented via metadata, which is so much more flexible than having to create classes
@djblue Were you particularly interested in implementing IDeref
or was the question a general question? Since IDeref
is Clojure only, I think we could get away with re-implementing that as a protocol in bb/sci
I got the re-routing through another fn working, but I'm not sure how to register the new protocol
https://github.com/djblue/sci/commit/c84e708e794d9e6c877b0addada3d0bb801eb202 is my current progress
Take a look at babashka.impl.protocols
There we define a multimethod for each protocol method:
(defmulti datafy types/type-impl)
Then we dispatch on some key which indicates this is a "reified" thing:
(defmethod datafy :sci.impl.protocols/reified [x]
(let [methods (types/getMethods x)]
((get methods 'datafy) x)))
(defmethod datafy :default [x]
;; note: Clojure itself will handle checking metadata for impls
(d/datafy x))
And for all other things we defer to the original datafy
How we we get sci to resolve the new protocol?
Look at the bottom of that file. There we insert "vars" into the namespaces
so then it will call our multimethod instead of the core var
so we have to insert our deref
multimethod into clojure.core
of sci
So I have that part working, it sees the deref method
the protocol IDeref
itself is also just a var that points to a map with methods
Where should that be registered?
sci.impl.namespaces?
hmm I see your point
clojure.lang.IDeref
is the class name right?
Yes, the interface, not sure if that matters :thinking_face:
I think I also patched import
so it can import protocols (which also generate interfaces in Clojure)
I'd have to take a look
I'll see how import works
Thanks!
https://github.com/borkdude/sci/blob/master/test/sci/records_test.cljc#L67-L77
I don't know if we already covered the case of importing an interface which is implemented as a protocol/multimethod in sci
but if you get stuck on this, I'd be happy to take a stab at it
Well I'm done looking into it today, might have time to continue tomorrow. I wouldn't be sad if you figured it out before I did 😄
I might
ah, we have something called resolve-record-class which is used to determine if a class represents some defrecord that sci created. we try that, if the class can't otherwise be resolved
maybe we can have something like resolve-protocol-class as well, as a fallback to normal classes. I'll think about it some more
can you PR your existing work to the IDeref branch of sci?
I probably won't get to this until the weekend
IDeref
and IAtom
I was going to refiy them and wire them into portal 👌
@portal
will give you the current value in portal and swap!
and reset!
will let you update it
repl -> portal -> repl ...
why not use a normal atom and add-watch?
or some functions around the state
The state isn't in the user's runtime, it's in the client runtime. So it isn't a real atom 😅
Right, but you can write a client API for this right? api/view
, api/swap!
etc
True, but clojure.core/swap!
is available everywhere in the same way clojure.core/tap>
This is not a technical limitation, just a developer experience experiment
When I'm debugging I tend to be focused on my problem and I reach for what's easy, mostly prn
and tap>
ok. it would be interesting if we could make this work. an overview of functions in Clojure that use deref and the swap/reset should be made, but I think they are mostly used directly?
they should then be re-routed through the protocol
similar to what was done for Datafiable etc
That solution makes sense
What's a good place to start looking at how to do this?
let's start with just deref. A protocol should be inserted named IDeref, in the same manner how we did Datafiable / Navigable. They are implemented as a multimethod under the hood (because that doesn't require compilation and we can't do compilation in GraalVM).
so you can look up the commit for those in sci and see how that was done
Ok, thanks for the info!
next, we have to patch deref
with a function that checks if the object implements our thing, if so, then we call our multimethod, if not, we call normal clojure deref