Functional-navigational programming in Clojure with Specter
nathanmarz.com
nathanmarz.com
Every language needs a lens/Specter. If you're spending a lot of time messing with data, this article will go directly to your heart.
If you examine these paths on their own then you get into general "optics" which is where the lens type lives and where specter lives (it seems). In particular, we start talking about generalized folds and traversals.
> At its simplest, a lens is a value representing maps between a complex type and one of its constituents. This map works both ways—we can get or "access" the constituent and set or "mutate" it. For this reason, you can think of lenses as Haskell's "getters" and "setters", but we shall see that they are far more powerful.
I thought I could get by with just using:
(-> huge-thing :key1 :key2 first ...)
But now I actually grok the value behind Specter! Great stuff, and I will use it next time I begin up a re-frame[2] app!"Specter: overcome your fear of nested Clojure data" https://www.youtube.com/watch?v=mXZxkpX5nt8
Yep!
On the fancy Haskell lens hierarchy (hackage.haskell.org/package/lens) we have the notion of a fold or a "getter which touches multiple items". The existence of a fold for a type like `Fold s a` indicates that we can extract from the type `s` some number (0 to many) `a` values in sequence. This is the idea of "Foldable" in Haskell.
Given a foldable type `s` and a transducer we execute the transducer by passing the "build" reducer in and then "visiting" each value `a` inside of `s` with the reducer that the transducer returns (modulo the early stopping bit which is just sort of a Clojure-specific optimization). Essentially, the transducer is a notion of "visitation" which is invariant to how the final summary is constructed—essentially the same thing that's captured in the "getter which touches multiple items" of a Fold.
So there really ought to be a way to treat any specter optic as a possibly very limited transducer. Essentially, the "read" component of a lens will correspond pretty directly.
We can also see this by remembering that any pure transducer is semantically equivalent to a function `a -> [b]` which you can read as a way of finding 0-to-many `b` values "inside" of `a`.
I'd love to be able to describe features of a data structure and have all qualifying elements be transformed, without my having to tell the select function how to get to those data structures in the first place; just hand it a data structure, and have it grovel through looking for matching subsets, then transforming them as desired.
Sort of like a grammar/DSL for Clojure data structures, without having to be specified from the basic character level up.
(select (walker number?) {2 [1 2 [6 7]] :a 4 :c {:a 1 :d [2 nil]}})
=> [2 1 2 1 2 6 7 4]
I've personally found this to be a use case that comes up infrequently.
Here's another that seems similar to it: an immutable language wherein you could create a mutable, write-only copy of an object. After writing all that was required, the copy could be finalized, at which point it becomes read-only (hence immutable). This is a bit like the builder pattern, but I've never seen a language with built-in support for it.
Thoughts? Is it much different than what the OP proposes?
Common Lisp is a multi-paradigm programming language, arguably more so than Clojure because it has a fully featured multi dispatch object system as well.
That said, I'm a little sad to hear that lenses remind you of an XML technology. :) I suppose I'd rather that XSLT be perceived as a very particular way of doing data transformation under particular conditions with a particular syntax.
All of the above said, I think good lessons could (in theory) be learned from XML, but I feel like XML's notational particulars get over-accentuated and the underlying thinking about data structures gets muddled and even lost.
I just realized that when I think of 'XSLT' I tend to think of it in terms of the combination of XSLT and XPath.
The major conceptual difference I can see is the ability for (apparent) in-place editing/updating, the lack of which was/is a major pain point in XSLT.
As XSLT is a functional language for traversing and manipulating heterogenous trees, one can expect quite a lot similarities beneath the surface syntax for most solutions targeting the same problem. At least in part because the limitations of the users (us) and the problem to be solved tends to constrain the solution space for generic (library like) solutions rather strongly.