Wow, I love this. My impression is that Specter is (at least in main use cases) Haskell's lens package. But while lens is very inside-baseball (at least in the tutorials I read), this explanation describes an extremely useful principle.
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.
> 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've read about specter before on its github[1], and I must admit I didn't see the bigger picture that is explained in the opening example of this article.
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!
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 like Specter, but it doesn't quite give me the holy grail it promises. My main pain-point is that it assumes I know the route to the datapoint I want to change. What if all I know is the general shape of a subset of data, and also that that shape might appear none or more times in my client dataset?
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.
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?
This is pretty nifty. What is so exciting about Clojure is that the language does fully realize the original lisp intention of creating building blocks on which any paradigm or style of programming can be built.
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.
The other name for this kind of abstraction is a 'lens' (as touched on at the bottom) - I found this library pretty quickly: https://github.com/DrBoolean/lenses
Regular functions are treated as filters, so that's done so that things like ALL, LAST, etc. are clearly delineated as something different than filters.
Lenses (as a construct) can encompass traversal, getting, setting, and transformation. With this in mind, I'd suggest that lenses are closer to a combination of XPath and XSLT. A key difference is that no schema is imposed.
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.
[+] [-] rtpg|10 years ago|reply
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.
[+] [-] dj-wonk|10 years ago|reply
> 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.
[+] [-] skratlo|10 years ago|reply
[+] [-] escherize|10 years ago|reply
I thought I could get by with just using:
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![1] https://github.com/nathanmarz/specter
[2] https://github.com/Day8/re-frame/
[+] [-] myth_drannon|10 years ago|reply
"Specter: overcome your fear of nested Clojure data" https://www.youtube.com/watch?v=mXZxkpX5nt8
[+] [-] tel|10 years ago|reply
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`.
[+] [-] OliverM|10 years ago|reply
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.
[+] [-] nathanmarz|10 years ago|reply
(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.
[+] [-] norswap|10 years ago|reply
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?
[+] [-] programnature|10 years ago|reply
[+] [-] moomin|10 years ago|reply
https://github.com/ctford/traversy/blob/master/README.md
[+] [-] dm3|10 years ago|reply
[+] [-] hellofunk|10 years ago|reply
[+] [-] Guthur|10 years ago|reply
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.
[+] [-] tel|10 years ago|reply
[+] [-] oweiler|10 years ago|reply
[+] [-] njs12345|10 years ago|reply
[+] [-] dj-wonk|10 years ago|reply
[+] [-] nathanmarz|10 years ago|reply
[+] [-] porker|10 years ago|reply
[+] [-] porker|10 years ago|reply
[+] [-] galois198|10 years ago|reply
[+] [-] aghillo|10 years ago|reply
[+] [-] herrvogel-|10 years ago|reply
[+] [-] th0ma5|10 years ago|reply
[+] [-] billrobertson42|10 years ago|reply
[+] [-] dj-wonk|10 years ago|reply
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.