Thinking about creating custom map types (thanks @wilkerlucio forhttps://blog.wsscode.com/guide-to-custom-map-types/) and after balking at the amount of code I'd have to re-implement using CLJS, I came up with this strategy I haven't seen talked about using specify .
See 🧵 for code.
;;
;; Example delay-map custom map type
;;
(defn- delay-map-entry
[me]
(specify me
IMapEntry
(-key [o]
(-key me))
(-val [o]
(let [v (-val me)]
(if (delay? v)
@v
v)))))
;; necessary for `specify` above to work
(extend-type MapEntry
ICloneable
(-clone [^MapEntry me]
(MapEntry. (.-key me) (.-val me) (.-__hash me))))
(defn delay-map
[m]
(specify m
IAssociative
(-assoc [o k v]
(delay-map (-assoc m k v)))
IFind
(-find [o k]
(delay-map-entry (-find m k)))
ILookup
(-lookup
([o k]
(let [v (-lookup m k)]
(if (delay? v)
@v
v)))
([o k nf]
(let [v (-lookup m k nf)]
(if (delay? v)
@v
v))))
ISeqable
(-seq [o]
(map delay-map-entry (-seq m)))
ISeq
(-first [o]
(delay-map-entry (first m)))
(-rest [o]
(map delay-map-entry (rest m)))))
In just my REPLing I haven't found any issues with this yet, curious if other people have tried this or see any obvious foot guns here.the nice properties: • It prints like a map, upgrades itself from vector to hash map, etc. • Only override the things you care about • You can add additional protocols as well, just like a custom type • Small amount of code
the less nice properties:
• it's not a distinct type, so you might surprise people with different functionality
• some nominal overhead with constructing the map then clone ing it
Your observation about portability to ClojureScript was apt ... then you overcame a challenge with Babashka ... I don't know whether you've tried ClojureCLR... and ClojureDart might be next. This is, in sum, a worthy academic pursuit but on the pragmatic side it battles stiff headwinds.
I think there's already potemkin's def-map-type for clojure, so I'm less worried about having a shared impl
Yeah if that works def grab for that
And that already works for the delay map use case, right?
Records also provide a good "extensible map" for some use cases
specify is awesome, but if you want to get similar behavior across both clj and cljs, then it'll be hard to port your new thing to clj as there's no specify in clj.
You could look into what I did here: https://github.com/johnmn3/ti-yong/blob/main/src/ti_yong/alpha/dyna_map.cljs
It's a little messy because I was just using it to make something else (function transformer things) but at some point I'll clean it up and make it its own lib. Feel free to copy out the bits you need - you could remove all the fn stuff (it's disabled by default anyway). I haven't done any optimization work either, so you might want to do some measurements