Finally I have reason to use specter in anger, for real, and my now-calm-as-cold-steel nervous system thanks @nathanmarz 🍻 . (For example... see thread comment.)
This is so much better than...
(def some-toplevel-lookup-table
(specter/multi-transform
[specter/MAP-VALS
(specter/multi-path
[:nested-1 (specter/terminal (fn [the-map]
{:itself the-map
:transformed (do-something the-map)}))
:nested-2 (specter/terminal (fn [the-other-map]
{:itself the-other-map
:transformed (do-something-else the-other-map)}))])]
{::big-1 {:nested-1 'map-p
:nested-2 'map-k}
::big-2 {:nested-1 'map-r
:nested-2 'map-d}
;; ...
;;
;; it's big
;;
;; ...
}))
Than this...
(def some-toplevel-lookup-table
(update-vals
{::big-1 {:nested-1 'map-p
:nested-2 'map-k}
::big-2 {:nested-1 'map-r
:nested-2 'map-d}
;; ...
;;
;; it's big
;;
;; ...
}
(fn [{:keys [nested-1 nested-2]}]
{:nested-1 {:itself nested-1
:transformed (do-something nested-1)}
:nested-2 {:itself 'map-1
:transformed (do-something-else nested-2)}})))Better in these ways:
1. Quality of life... the transform is written at the very top, so I can visually tell what the transformed map ought to look like, instead of having to scroll past <giant map>.
2. Specification... i can optionally lift out and specify the transform functions... (fn [the-map] ...) and (fn [the-other-map] ...) , which is semantically cleaner than specifying (fn [:keys [nested-1 nested-2] ...), as well as more maintainable. If I add a transform, simply adding a /new/ function and function specification "just works" in the "better" case.
3. Potentially quicker, as well as friendlier to GC (or at least no slower/worse than standard lib operations), if I have to do such things at run-time (based on specter's own benchmarks).
And I can't help but echo the library's sentiment... Language support for good enough macrology is why I stick with Clojure (and Lispy langs), because it lets people build "seamless" language extensions (like specter), for user needs that are hard to predict in advance, when the language is on the drawing board.
While I acknowledge the value of metaprogramming, without being familiar with Specter, my question is: it's just a lens library, no? It seems like something one could do without macros, but that's a guess.
Yes one could... However, with macros one can do targeted, domain-aware (because, it's a DSL), compile-time work that the standard compiler (and standard library functionality) does not do. For example. Specter's inline caching implementation: https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation
I will have to check it out eventually. I have only ever very briefly experimented with lens in Haskell, whence I believe they originate. As a "pattern," they seem interesting to me, but like monads (something else people have ported to various dynamically-typed languages), it appears less useful without type checking. My understanding is that Haskell's records are pretty broken, but I guess my intuition remains that something about complex data traversals is better-suited to the compile-time guarantees that a type system like Haskell's provides