Fork me on GitHub

If I only use reify to implement a Protocol, is it safe to replace satisfy? calls with instance?. Seems to work at least and much cleaner than the cache-pattern using Object. Few recent library cases on hacking around the slowness: • Lacinia: • Malli:

Alex Miller (Clojure team)12:09:26

Why are you even using a protocol then and not just definterface or whatever?

Alex Miller (Clojure team)12:09:00

satisfies? is never going to be a fast path. The whole idea with polymorphic dispatch is to lean on the fast polymorphic dispatch - make the call and implement the appropriate fallbacks. So I guess, I’d first ask why you can’t avoid satisfies in the first place.


Good point. in malli, most protocols are internal and could be interfaces too (haven't done interfaces with cljs, guess they work the same?), but some need to be polymorphic. Thinking of checking first the instance (fast path) and fallbacking to normal satifies?. Lacinia is doing the same ( Having fast satisfies? in core might speed up a lot of clojure libs & apps.


It’s been a while, but Iirc cljs does not have definterface. You can lean on closure compiler interface annotations for some type checking but implementation is just adding methods to Object in your deftype. I agree re having fast satisfies, I’ve been bitten by this before. Are you aware of ztellman’s version?


was not, thanks for sharing!

Alex Miller (Clojure team)14:09:58

In most cases, code that is slow because of satisfies should not be using satisfies

Alex Miller (Clojure team)14:09:51

Why do you need satisfies?


to create extendable system for both clj + cljs. Fast by default.

Alex Miller (Clojure team)17:09:03

It’s extensible just based on protocols. I meant in what scenario is satisfies needed over just making a polymorphic call?


oh, basically for two things: 1. offer a satisfies? -like helper 2. offer an api that doesn’t throw if called with something that doesn’t satisfy?

;; 1
(defn schema? [x] (#?(:clj instance?, :cljs implements?) malli.core.Schema x))

;; 2
(defn entries
   (entries ?schema nil))
  ([?schema options]
   (if-let [schema (schema ?schema options)]
     (if (#?(:clj instance?, :cljs implements?) malli.core.MapSchema schema)
       (-entries schema)))))


could add a default impl for 2, but then coudn’t do 1 with it (as everything satisfies it)


could add a to protocols to solve 1 & 2, but doing that with 10+ protocols is a lot of boilerplate


tried the satisfies-cache you mentioned earlier, but it’s also a lot of boilerplate for all protocols.


just would like to have fast satisfies?:)

Alex Miller (Clojure team)17:09:37

why do you need to do 1?


ad-hoc polymorphism I guess: branching with a predicate with cond, clojure.walk/prewalk with special logic for all things that satisfy etc.


many cond-cases could all modelled purely with new protocols, but they add a lot of js bundle size on cljs, trying to find balance between protocols and satify?+ conds.

Alex Miller (Clojure team)17:09:24

I'm trying to get at the heart of the problem, feel like we're finally getting close


the ad-hoc case look useually like this:

;; 1
  (ref? x) ...
  (schema? x) ...
  (vector? x) ...
  :else ...)
that could be implemented with a new protocol, but would lead to huge amount of protocols.

Alex Miller (Clojure team)18:09:16

are those ...'s all doing the same action that could be a different protocol?