Fork me on GitHub
#clojurescript
<
2022-09-04
>
Lidor Cohen13:09:36

Hi, is it possible to override map accessors for a specific tagged map (hope I got my terminology correct)?

(def my-map #some-tag{:a 1 :b 2})

(my-map :a) => some specific logic
(:a my-map) => the same specific logic
(get-in my-map [:a]) => the same specific logic

p-himik13:09:11

#some-tag invokes a data reader for some-tag which should return an instance of some data type. What you can and cannot do with that data type then depends on that particular type.

Alex Miller (Clojure team)13:09:42

I would say that you should look suspiciously on desires to do so

Alex Miller (Clojure team)13:09:03

If you need custom logic, make a function

Lidor Cohen13:09:19

I want to create some kind of lazy map that evaluates (and also access itself) on access. I realize I don't want to override the core map, hence the tag, to separate it and treat it as a special type, but I do want as less deviation as possible from the core map API. I'm still a beginner so I might be using the wrong instruments for the task...

agile_geek14:09:23

I'm with @U064X3EF3 on this one. I would ask why you need this facility? What you've presented is a solution to a problem you may have. Without understanding why you think you need to specialise map in the first place it's hard to suggest a more idiomatic approach. Normally you would write a function that takes a map as an argument and does whatever you require.

Lidor Cohen14:09:46

Well, I wrote about it here: https://clojurians.slack.com/archives/C03S1L9DN/p1661716278630149 The gist is that on our company we work extensively with a data structure the requires self-ref and evaluate on access and be open to the std map api (get-in, assoc, merge, etc...).

rolt14:09:08

i've seen a lib that implement this data type

Lidor Cohen14:09:30

That would be awesome 🙏 😊

Ferdinand Beyer15:09:19

Sounds like you want to define your own type that behaves like a map but is not really a map. You can deftype your own type and implement all ClojureScript protocols that PersistentHashMap does: https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L7995

Lidor Cohen15:09:50

Yeah, that's probably what I want. Is it possible to only override the access method and inherit all other implementations? Or must I specify each and every method?

Ferdinand Beyer15:09:56

You must provide all protocols for the behavior you want. There is no inheritance. You can probably leave out quite a few, such as IMeta if you don’t require your type to carry metadata.

Lidor Cohen16:09:07

I don't want it to bite me back when someone will use some functionality that is supposed to work with a map but won't work for my map, so I guess I'll have to implement the entire interface...

Lidor Cohen11:09:32

So I'm trying to experiment with it (at worst I'll just earn a better understanding of cljs type & protocols system).

(def ^:private lookup-sentinel (js-obj))
  (deftype LazySelfrefMap [meta cnt root ^boolean has-nil? nil-val ^:mutable __hash]
    Object
    (toString [coll]
      (pr-str* coll))
    (equiv [this other]
      (-equiv this other))

  ;; EXPERIMENTAL: subject to change
    (keys [coll]
      (es6-iterator (keys coll)))
    (entries [coll]
      (es6-entries-iterator (seq coll)))
    (values [coll]
      (es6-iterator (vals coll)))
    (has [coll k]
      (contains? coll k))
    (get [coll k not-found]
      (-lookup coll k not-found))
    (forEach [coll f]
      (doseq [[k v] coll]
        (f v k)))

    ICloneable
    (-clone [_] (PersistentHashMap. meta cnt root has-nil? nil-val __hash))

    IIterable
    (-iterator [coll]
      (let [root-iter (if ^boolean root (-iterator root) (nil-iter))]
        (if has-nil?
          (HashMapIter. nil-val root-iter false)
          root-iter)))

    IWithMeta
    (-with-meta [coll new-meta]
      (if (identical? new-meta meta)
        coll
        (PersistentHashMap. new-meta cnt root has-nil? nil-val __hash)))

    IMeta
    (-meta [coll] meta)

    ICollection
    (-conj [coll entry]
      (if (vector? entry)
        (-assoc coll (-nth entry 0) (-nth entry 1))
        (loop [ret coll es (seq entry)]
          (if (nil? es)
            ret
            (let [e (first es)]
              (if (vector? e)
                (recur (-assoc ret (-nth e 0) (-nth e 1))
                       (next es))
                (throw (js/Error. "conj on a map takes map entries or seqables of map entries"))))))))

    IEmptyableCollection
    (-empty [coll] (-with-meta (.-EMPTY PersistentHashMap) meta))

    IEquiv
    (-equiv [coll other] (equiv-map coll other))

    IHash
    (-hash [coll] (caching-hash coll hash-unordered-coll __hash))

    ISeqable
    (-seq [coll]
      (when (pos? cnt)
        (let [s (if-not (nil? root) (.inode-seq root))]
          (if has-nil?
            (cons (MapEntry. nil nil-val nil) s)
            s))))

    ICounted
    (-count [coll] cnt)

    ILookup
    (-lookup [coll k]
      (-lookup coll k nil))

    (-lookup [coll k not-found]
      (cond (nil? k)    (if has-nil?
                          nil-val
                          not-found)
            (nil? root) not-found
            :else       (.inode-lookup root 0 (hash k) k not-found)))

    IAssociative
    (-assoc [coll k v]
      (if (nil? k)
        (if (and has-nil? (identical? v nil-val))
          coll
          (PersistentHashMap. meta (if has-nil? cnt (inc cnt)) root true v nil))
        (let [added-leaf? (Box. false)
              new-root    (-> (if (nil? root)
                                (.-EMPTY BitmapIndexedNode)
                                root)
                              (.inode-assoc 0 (hash k) k v added-leaf?))]
          (if (identical? new-root root)
            coll
            (PersistentHashMap. meta (if ^boolean (.-val added-leaf?) (inc cnt) cnt) new-root has-nil? nil-val nil)))))

    (-contains-key? [coll k]
      (cond (nil? k)    has-nil?
            (nil? root) false
            :else       (not (identical? (.inode-lookup root 0 (hash k) k lookup-sentinel)
                                         lookup-sentinel))))

    IFind
    (-find [coll k]
      (cond
        (nil? k) (when has-nil? (MapEntry. nil nil-val nil))
        (nil? root) nil
        :else (.inode-find root 0 (hash k) k nil)))

    IMap
    (-dissoc [coll k]
      (cond (nil? k)    (if has-nil?
                          (PersistentHashMap. meta (dec cnt) root false nil nil)
                          coll)
            (nil? root) coll
            :else
            (let [new-root (.inode-without root 0 (hash k) k)]
              (if (identical? new-root root)
                coll
                (PersistentHashMap. meta (dec cnt) new-root has-nil? nil-val nil)))))

    IKVReduce
    (-kv-reduce [coll f init]
      (let [init (if has-nil? (f init nil nil-val) init)]
        (cond
          (reduced? init)          @init
          (not (nil? root)) (unreduced (.kv-reduce root f init))
          :else                    init)))

    IFn
    (-invoke [coll k]
      (-lookup coll k))

    (-invoke [coll k not-found]
      (-lookup coll k not-found))

    IEditableCollection
    (-as-transient [coll]
      (TransientHashMap. (js-obj) root cnt has-nil? nil-val)))

  (defn lazy-selfref-map
    "keyval => key val
  Returns a new hash map with supplied mappings."
    [& keyvals]
    (loop [in (seq keyvals), out (transient (.-EMPTY LazySelfrefMap))]
      (if in
        (let [in' (next in)]
          (if (nil? in')
            (throw (js/Error. (str "No value supplied for key: " (first in))))
            (recur (next in') (assoc! out (first in) (first in')))))
        (persistent! out))))
I copied the PersistentHashMap Implementation and just renamed it for starter to see if I get the same behavior under my type, but I failed pretty fast:
(.-EMPTY LazySelfrefMap) => nil

(transient (.-EMPTY LazySelfrefMap)) => 
:repl/exception!
; 
; Execution error (Error) at (<cljs repl>:1).
No protocol method IEditableCollection.-as-transient defined for type undefined: 

(lazy-selfref-map :a 1) => 
:repl/exception!
; 
; Execution error (Error) at (<cljs repl>:1).
No protocol method IEditableCollection.-as-transient defined for type undefined: 
.-EMPTY returned nil and transient threw an exception (because it didn't get any valid type). I tried to understand where's .-EMPTY implementation is but couldn't figure it out, can someone please help me shed some light, or even point me to some guide that will help me get started?

rolt12:09:53

the lib i mentionned: https://github.com/Malabarba/lazy-map-clojure (no self ref though)

Lidor Cohen12:09:34

Thank you, maybe I can learn how to implement a map from there 😄

paulocuneo16:09:07

Hi frontend amateur here, building a small app for friend. Frontend question: giving that the dynamic/lazy import exist, and it's posible to "load js as you go", what would be the advantage of building a Multi Page App? For context I been playing around with next.js and now I'm looking for something similar in cljs, I see shadowjs allows to do codespliting, if on top I could pre-render re-frame/reagent I would achieve the same functionality as next.js , also I don't see a point for SSR besides SEO. I know that if the app is small enough, SPA would do just fine 🙂, but this is a theoretical question, in case I ever need to build something bigger.

Philip Peterson21:09:59

Hello all, I am trying to use dvingo/cljs-styled-components {:mvn/version "0.1.11"} in a Reagent app. I added this to the deps.edn, and the docs say to include the following:

(:require [cljs-styled-components.reagent
              :refer [defstyled]])
However when running clj, it gives the following error message:
Unexpected error (ExceptionInfo) compiling at (REPL:1).
No such namespace: cljs-styled-components.reagent, could not locate cljs_styled_components/reagent.cljs, cljs_styled_components/reagent.cljc, or JavaScript source providing "cljs-styled-components.reagent" (Please check that namespaces with dashes use underscores in the ClojureScript file name) in file
Does anyone know what i may be doing wrong?

p-himik21:09:14

The group ID of that dependency is its name, not dvingo. So it should be cljs-styled-components/cljs-styled-components {:mvn/version "0.1.11"} in deps.edn.

Philip Peterson21:09:24

My bad, that was something I tried to fix the error but it actually doesn’t change anything. My deps.edn now reads cljs-styled-components/cljs-styled-components {:mvn/version "0.1.11"} and still the same error message.

p-himik21:09:39

FWIW, with shadow-cljs I get a different error:

The required JS dependency "styled-components" is not available, it was required by "cljs_styled_components/common.cljc".

Dependency Trace:
        app/core.cljs
        cljs_styled_components/reagent.cljc
        cljs_styled_components/common.cljc
And after running npm i styled-components, the build got fixed. No clue what's going on on your side.

p-himik21:09:27

Are you sure you e.g. added the dependency to the root :deps or to an alias that you 100% specify during the UI build?

Philip Peterson21:09:27

Oh hmm. Yeah I am using clj -M -m cljs.main -co build.edn -v -c -r so no shadow-cljs

p-himik21:09:21

Shouldn't really matter in this case - as long as that jar is on the classpath of the CLJS build process.

p-himik21:09:53

That looks alright. No clue, sorry.

👍 1
Philip Peterson21:09:07

’sok, thanks anyway!

paulocuneo23:09:44

in your deps.edn there's a misplaced braces }

❤️ 1
Philip Peterson02:09:09

Wow, great find, thank you. That’s crazy, so was it treating it as a sub-dependency of reagent/reagent or something?

Philip Peterson02:09:06

Oh, I guess it was just getting swallowed up? Curious, did you find this by staring at the source code or did you run a command to help debug?

paulocuneo02:09:16

It was placed as the reagent dep configuration, but ignored because it didn't match any configuration key