Tinholes – performant, strongly typed lenses in Clojure
blog.podsnap.com
blog.podsnap.com
Using macros to pre-compile the lenses is clever, but feels like a hack around typed-clojure instead of being aligned to it. All of the information needed to determine a lens' action is available at compile time even prior to expansion. Can a van Laarhoven representation be made in typed-clojure that recovers this information?
> type-safe, efficient, concise, idiomatic and flexible handling of deeply nested data structures, and that's what we got. Prima facie, these were unreasonable requests - it's not as though other languages were lining up to answer them
and since the line was crossed: yes, other languages are exactly lining up to answer all of those. You don't even have to burn-in the choice of lens. It's exciting to see this technology developing all around, but it'd also be nice to see it with a bit more humility.
I couldn't help but feel this way as I was reading it as well...forget compile time though. I wonder if there's any way/plans to eliminate this kind of type erasure period from typed Clojure eventually?
type Lens src tgt
compose :: Lens src int -> Lens int tgt -> Lens src tgt
and then chains of lenses composed with `compose` would carry the appropriate typing information to not have to fall back on Any. This doesn't work "idiomatically" in Clojure because the *-in commands want to consume a list of keys where (1) each key is untyped and (2) the list cannot be heterogenous like it must be for the typed API.That all said, it'd be easy to implement the typed API in Clojure as the "van Laarhoven" representation I mentioned has `Lens` just being a certain kind of function and `compose` just being regular (reversed) function composition: it's exactly like transducers in that regard (in fact, transducers can even be seen as a specific instance of a particular generalization of a lens, I believe).
The challenge is figuring out where the vtable for things like `map` lives. I'm pretty sure this is solvable in Clojure by using protocols (although I'm also pretty sure you can't get `Traverses` that way since you need return type polymorphism to get `pure` to work).
Anyway, it'd be neat to experiment. Maybe I'll teach myself some typed-clojure and see if I can't port the van Laarhoven lenses.
If you don't mind an extra layer of interpretation (which you might very well mind!) you can get around this by building up basically an AST of your applicative computation---I did this, more or less, in my monads-in-clj library: https://github.com/bwo/monads which has a kind of not-that-useful applicative definition: https://github.com/bwo/monads
I don't know too much about the innards of van Laarhoven lenses but I'd guess that a straight port might be inhibited by the fact that Clojure isn't pervasively lazy---so using Const as your functor, for instance, won't let you avoid actually computing the eventual second argument which is just discarded. But I dunno.
There shouldn't be a problem with strictness and van Laarhoven lenses. The particular trouble is just writing the lenses in the first place:
(def one [inj]
(fn [p]
(fmap (fn [f] (pair f (snd p)))
(inj (fst p)))))
where did the `fmap` come from? 1. Focusing on "the subtree" instead of "the value
at the subtree"
2. Working with data types like `{:a {:b {:c 5}}}` is
somewhat uncommon in Haskell (too little information
gets projected into the types)
3. Doing "along the way parsing"
Each of those are fine to do in Haskell and I could work up an example. Perhaps the best domain for this is using the lens-aeson[0] package since your data type is not so different from JSON. Here you can use the _JSON prism to do your "along the way parsing" (the failure mechanism of prisms catches failed parses naturally). You can also focus on whole subtrees naturally (since the JSON Value type is just a sum over subtrees and values all together, so the natural parametricity distinction is lost).So I'd look at [0] for a wealth of examples in that vein. I'd also look into zipper lenses. They were removed in more recent versions (I think anticipating splitting them out into another package) but they're available in the 3.* series [1].
[0] http://hackage.haskell.org/package/lens-aeson [1] http://hackage.haskell.org/package/lens-3.10.1/docs/Control-...
The existence of zipper lenses catches me by surprise, as I have been frequently directed at a stackoverflow [0] response on the difference between the two...
[0] http://stackoverflow.com/questions/22094971/what-are-the-dif...
"Type-checking is somewhat difficult with the current implementations of ph-whatever, as, intuitively, it would be with anything implemented as a recursive function that might consume and return values of different types at different levels of the stack. "
So the goal is to enable TypedClojure to work in some functions, such as assoc-in, that are recursive and difficult to type.
But I did read the article - do I understand correctly that lenses are more or less equivalent to the Common Lisp getf constructs?
(The methodology could be a bit nicer; it's a simple timed doseq, but it does show some performance gains.)
Although this word doesn't have a scientific gleam or a stamp of enterprise approval, it is actually more specific than "performant". Technical words are nice but sometimes they just serve to hide details. All that glitters is not gold.
I am not sure if that is the goal of the original statement, but the point stands nevertheless. All words were just made up at some point of time or another and it should be trivial to prove the set of words available in any language's vocabulary is a very small subset of the set of all ideas one could possibly express; for this reason I think the creation of new words should not be discouraged unless there is a compelling reason to do so.
In the end, the word accomplishes what language ought to: it conveyed an idea which was more or less understood by the target audience it was aimed out.
I disagree. See this use of 'fast' in a submission, for example: https://news.ycombinator.com/item?id=7132044
What does "fast" mean there? It could mean the development environments are performant. Or it could mean they're quick to implement or set up. I believe the latter is what is intended, but I can't be sure from the submission or even the article, because the word "fast" is not very specific. In this context, the word "performant" would have been much more clear.
Language is fuzzy and malleable and subjective. People shouldn't be so quick to lay down declarations of approved or preferred words. And to chide someone for using a word that hasn't made it to the dictionary yet is tantamount to declaring that we should freeze English and never change it (referring to the great-grandparent post here).