clojure-dev

2023-08-28T19:16:47.022639Z

i've been reading the code for defprotocol and defrecord and extend recently, and I'm unsure how they intersect. defprotocol generates a java interface and dynamically loads it (in genclass.clj). defrecord generates an interface as well using deftype*, which is handled by Compiler.java. I only sort of follow what's happening, but it seems like the compiled record interface has defined methods for all of the implemented protocols. And then extend just adds the implemented methods to the method cache on the protocol's var. how do these line up with each other?

✅ 1
2023-08-28T19:32:52.566779Z

Protocols are a composite of different satisfaction strategies

2023-08-28T19:34:34.893309Z

A given object can satisfy a protocol by implementing an interface, by having the protocol extended to some super type of the object, or by adding some special metadata to an object

2023-08-28T19:35:49.317749Z

Deftype implementing an interface is the interface route

2023-08-28T19:36:14.521319Z

Extend adding to the var is the extending to a super type route

2023-08-28T19:36:55.176719Z

The metadata route you'll see if you look at how protocol callsites are compiled (and protocols have to opts into supporting)

2023-08-28T19:38:52.350299Z

The metadata route is the most dynamic, the interface route has the best performance

2023-08-28T19:41:56.955769Z

that makes sense. thanks. when compiling a protocol method, is it purely the compiler that determines which route to build?

2023-08-28T19:43:06.022589Z

The compiler generates a callsite that potentially tries all 3

2023-08-28T19:43:52.937369Z

With an inline cache to try and avoid having to do that dispatch every time

2023-08-28T19:45:30.441389Z

I am not looking at it right now, so I don't recall if the inline cache covers all the cases, or just the extend case, and the jvm is expected to make the instance check for the interface fast

2023-08-28T19:49:52.630759Z

this is InvokeExpr, right? i think i'm looking at the right spot

2023-08-28T19:51:43.213189Z

I believe there is a distinct Expr type for protocol callsites

2023-08-28T19:52:15.842749Z

InvokeExpr has an emitProto, I haven't seen a different one but this is a really big file and there's plenty to miss

2023-08-28T19:52:41.773069Z

Yeah, I could be mistaken

2023-08-28T19:52:54.926799Z

no worries. this is really helpful, thank you

2023-08-28T19:58:06.896089Z

Yeah, I think I am muddling how the cache for keyword callsites work with the protocol sites

👍 1
2023-08-28T20:04:51.597759Z

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L3750-L3755 is the calling a protocol function (what does the extends dispatch logic)

👍 1
2023-08-28T20:07:05.187019Z

the metadata bits must be tucked away inside the functions together with the extends stuff

👍 1
2023-08-28T20:23:28.466439Z

here's my understanding leading up to that: emit-protocol defs (interns) the symbol names of the protocol methods with maps, including {:protocol (var ~name)} (the protocol itself). at construction time, InvokeExpr looks for that metadata and looks at the protocol's :on to see if it's an actual Class. If it is, grab the :method-map which is a map of the method names as keywords to the :on class, get the class instance using the protocol method from the map, and store it on the InvokeExpr object.

2023-08-28T20:28:02.653219Z

then in emitProto as you noted, if the protocol actually implements the interface it emits an interface call, and if it doesn't it emits a "normal" clojure function call to the protocol var, which is the function produced by emit-method-builder , where the method cache stuff happens

2023-08-28T20:29:23.556729Z

hell yeah, thank you for helping out, i don't think I would have put together how emitProto works without your help