Fork me on GitHub
#clojurescript
<
2021-06-09
>
sh5412:06:16

I was looking into porting over https://github.com/stuartsierra/clojure.walk2 to clojurescript. My initial tests show a 2-5x speedup. But I am having some issues dispatching records correctly. There are certainly some clojure/script nuances around protocols that I am not up to speed on. So here is a little clojure to clojurescript comparison getting at the crux of the matter: (this example code is just mimicking a portion of the walk2 code. Clojure

(defprotocol SomeP
  (report [coll]))

(extend-protocol SomeP
  java.lang.Object
  (report [x]
    (println :SomeP :path :object :is-record? (record? x)))
  clojure.lang.IRecord
  (report [x]
    (println :SomeP :path :record)))

(extend-type clojure.lang.PersistentArrayMap
  SomeP
  (report [x]
    (println :SomeP :path :map)))

(defrecord Foo [a b c])

(report 1234)
;; :SomeP :path :object :is-record? false

(report {:a 1})
;; :SomeP :path :map

(report (map->Foo {:a 1 :b 2 :c 3 :extra 4}))
;; :SomeP :path :record
Clojurescript
(defprotocol SomeP
  (report [coll]))

(extend-protocol SomeP
  object
  (report [x]
    (println :SomeP :path :object :is-record? (record? x)))
  number
  (report [x]
    (println :SomeP :path :number))
  cljs.core.IRecord
  (report [x]
    (println :SomeP :path :record)))

(extend-type cljs.core.PersistentArrayMap
  SomeP
  (report [x]
    (println :SomeP :path :map)))

(defrecord Foo [a b c])

(report 1234)
;; :SomeP :path :number

(report {:a 1})
;; :SomeP :path :map

(report (map->Foo {:a 1 :b 2 :c 3 :extra 4}))
;; :SomeP :path :object :is-record? true
Without the extend on object then (report (map->Foo {...})) fails so the extend on cljs.core.IRecord does nothing. So my question is in clojurescript can I use extend-protocol to work on any record type? I can always just check with record? when the record gets dispatched as an object but I was hoping to avoid that.

dnolen13:06:38

@slack1003 it cannot be avoided - you cannot extend a protocol w/ a protocol which is what you're trying to do

dnolen13:06:12

in Clojure this works due to protocols being mapped to Java interfaces

sh5413:06:18

k that makes sense. Thanks for the info!

jhacks15:06:13

Is it possible to test for whether an object is a JavaScript object or a ClojureScript object? I would like to test when something is going to print as an #object at the REPL, for example:

> (-> js/document (.getElementById "63R"))
    #object[HTMLSelectElement [object HTMLSelectElement]]
I tried instance?, but that doesn’t distinguish ClojureScript objects:
> (instance? js/Object. (-> js/document (.getElementById "1120R")))
    true

    > (instance? js/Object. {})
    true
I tried object?, but that doesn’t work:
> (object? (-> js/document (.getElementById "1120R")))
    false

dnolen15:06:31

there's a low level thing cljs$lang$type boolean field

thheller15:06:15

technically you can also extend the printer protocols on JS types, so even though it may not be a CLJS type that doesn't mean it'll guaranteed print as #object

jhacks15:06:42

Forgive my ignorance, but how do I check for the field?

> (.-cljs$lang$type js/Object.)
nil
> (.-cljs$lang$type {})
nil

borkdude15:06:37

@jhacks

cljs.user=> (defrecord Foo [])
cljs.user/Foo
cljs.user=> (.-cljs$lang$type Foo)
true

jhacks15:06:50

Ah, I need to use type with it. Thanks @dnolen, @thheller, and @borkdude!

> (.-cljs$lang$type (type js/Object.))
nil

> (.-cljs$lang$type (type {}))
true