specter

2025-04-21T12:57:27.250309Z

Hi @nathanmarz my debugging led me to an interesting discovery 🧐 Here is a small replication: first just the same as ATOM but prints on transform*

(defnav
  ^{:doc "Navigates to atom value."}
  ATOM
  []
  (select* [this structure next-fn]
           (next-fn @structure))
  (transform* [this structure next-fn]
              (do
                (println structure)
                (swap! structure next-fn)
                structure)))
now for the test:
(def travers-parents (sp/recursive-path [] p (sp/stay-then-continue [(sp/must :parent) p]))) ;; some tree traversal

(def specter-travers-test
  {:parent {:state (atom "first")
            :parent {:state (atom "second")}}})

(comment
  (sp/setval [(sp/subselect [travers-parents (sp/must :state) ATOM]) sp/FIRST]
             "changed"
             specter-travers-test)
  ; #object[cljs.core.Atom {:val first}]
  ; #object[cljs.core.Atom {:val second}]
  ;;=> {:parent {:state #object [cljs.core.Atom {:val "changed"}], :parent {:state #object [cljs.core.Atom {:val "second"}]}}}

  (sp/select [:parent :state ATOM] specter-travers-test)
  ;;=> ["changed"]

  (sp/select [:parent :parent :state ATOM] specter-travers-test)
  ;;=> ["second"]

  ;; 
  )
So what I show here basically is that setval used on [(sp/subselect ...) sp/FIRST] actually seem to "touch" all the results of the subselect but in a way that doesn't change the value, but it does touch them. This can be invisible if transform* doesn't have a side-effect. Is this expected?

nathanmarz 2025-04-21T16:06:35.362979Z

yes, that's how it works