Fork me on GitHub
#babashka
<
2020-09-05
>
djblue07:09:54

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:

borkdude07:09:10

@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

👌 1
djblue07:09:28

How hard would supporting interfaces be?

borkdude07:09:17

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

👌 1
borkdude07:09:36

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

1
borkdude07:09:27

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?

djblue07:09:31

I wonder if maybe we can proxy the interfaces via protocols?

borkdude07:09:50

hmm, interesting

borkdude07:09:57

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

borkdude07:09:21

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

djblue07:09:06

Thanks for all the context, I'll keeping thinking about this problem.

borkdude07:09:53

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 :)

borkdude07:09:11

Protocols can now even be implemented via metadata, which is so much more flexible than having to create classes

💯 1
borkdude20:09:02

@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

djblue20:09:38

IDeref and IAtom

djblue20:09:58

I was going to refiy them and wire them into portal 👌

djblue20:09:32

@portal will give you the current value in portal and swap! and reset! will let you update it

djblue20:09:59

repl -> portal -> repl ...

borkdude20:09:21

why not use a normal atom and add-watch?

borkdude20:09:01

or some functions around the state

djblue20:09:50

The state isn't in the user's runtime, it's in the client runtime. So it isn't a real atom 😅

borkdude20:09:35

Right, but you can write a client API for this right? api/view, api/swap! etc

djblue20:09:40

True, but clojure.core/swap! is available everywhere in the same way clojure.core/tap>

djblue20:09:04

This is not a technical limitation, just a developer experience experiment

djblue20:09:36

When I'm debugging I tend to be focused on my problem and I reach for what's easy, mostly prn and tap>

borkdude20:09:06

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?

borkdude20:09:46

they should then be re-routed through the protocol

borkdude20:09:21

similar to what was done for Datafiable etc

👌 1
djblue20:09:09

That solution makes sense

djblue20:09:50

What's a good place to start looking at how to do this?

borkdude20:09:02

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).

👍 1
borkdude20:09:17

so you can look up the commit for those in sci and see how that was done

djblue20:09:57

Ok, thanks for the info!

borkdude20:09:04

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

👍 1
djblue06:09:25

I got the re-routing through another fn working, but I'm not sure how to register the new protocol

borkdude07:09:20

Take a look at babashka.impl.protocols

borkdude07:09:13

There we define a multimethod for each protocol method:

(defmulti datafy types/type-impl)

borkdude07:09:59

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))

borkdude07:09:17

And for all other things we defer to the original datafy

djblue07:09:51

How we we get sci to resolve the new protocol?

borkdude07:09:24

Look at the bottom of that file. There we insert "vars" into the namespaces

borkdude07:09:25

so then it will call our multimethod instead of the core var

borkdude07:09:13

so we have to insert our deref multimethod into clojure.core of sci

djblue07:09:48

So I have that part working, it sees the deref method

borkdude07:09:58

the protocol IDeref itself is also just a var that points to a map with methods

djblue07:09:10

Where should that be registered?

djblue07:09:33

sci.impl.namespaces?

borkdude07:09:38

hmm I see your point

borkdude07:09:59

clojure.lang.IDeref is the class name right?

djblue07:09:23

Yes, the interface, not sure if that matters :thinking_face:

borkdude07:09:05

I think I also patched import so it can import protocols (which also generate interfaces in Clojure)

borkdude07:09:20

I'd have to take a look

djblue07:09:34

I'll see how import works

borkdude07:09:08

I don't know if we already covered the case of importing an interface which is implemented as a protocol/multimethod in sci

borkdude07:09:24

but if you get stuck on this, I'd be happy to take a stab at it

djblue07:09:06

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 😄

borkdude07:09:52

I might

💯 1
borkdude07:09:45

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

borkdude07:09:13

maybe we can have something like resolve-protocol-class as well, as a fallback to normal classes. I'll think about it some more

👍 1
borkdude08:09:48

can you PR your existing work to the IDeref branch of sci?

👍 1
borkdude08:09:55

I probably won't get to this until the weekend

👌 1