clojurescript

lilactown 2024-10-20T17:02:25.226029Z

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.

👀 1
lilactown 2024-10-20T17:03:29.590769Z

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

lilactown 2024-10-20T17:07:15.592019Z

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

lilactown 2024-10-20T17:09:51.010039Z

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

2024-10-20T17:21:15.631979Z

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.

lilactown 2024-10-22T17:06:22.857159Z

I think there's already potemkin's def-map-type for clojure, so I'm less worried about having a shared impl

john 2024-10-22T17:13:38.801249Z

Yeah if that works def grab for that

john 2024-10-22T17:19:42.392529Z

And that already works for the delay map use case, right?

john 2024-10-22T17:22:02.897789Z

Records also provide a good "extensible map" for some use cases

john 2024-10-22T02:48:40.262799Z

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