
On Thu, Feb 9, 2012 at 10:03 PM, Donn Cave
Quoth Evan Laforge
, On Thu, Feb 9, 2012 at 12:49 PM, Donn Cave
wrote: ... For example, in a better world you could write stuff like
modifyConfig :: (Config -> a) -> (a -> a) -> Config -> Config modifyConfig fr fv a = a { fr = fv (fr a) }
upTempo config = modifyConfig tempo (+ 20) config
I think lenses already do better than this, since not only are they more concise than the above (once you've resigned yourself to a few TH splices), they aren't restricted to being only record fields.
How more concise? Because =# is more concise than `modifyRecord', etc., or is there some real economy of expression I missed out on? Asking because, honestly I didn't get your earlier example -
More concise because in your example (which is also what most of my code looks like), you define a modifyX function and then apply it to form the setField function. To be complete you would have to define a modifyX for every branch in the nested records. It's rare for records go above 3 levels deep, but you can still wind up with quite a function boilerplate style modifyX functions. In the case of lenses, all the relevant modifyX functions are generated automatically and can be composed.
setTempo :: Config -> Config setTempo y = Config.deflt#Config.tempo =# y
... something's missing, I thought - but maybe it's conciser than I can reckon with!
Getting rid of the special operators and eta reduction might make it clearer: setTempo y config = set (Config.tempo `composeLens` Config.deflt) y config
The rest - the functions that look like fields, the enforcing invariants, etc. - are cool as lens features, but for Haskell records in general it seems like something that would call for a lot of discussion. Compared to first class record update, where it's easy to see how close to broken the current situation is.
Well, that's why I'm saying we don't have to build the lens features into the language, though I think at some point one of those lens libraries should make it into the platform and be encouraged as the standard way. I think the field access / modification problem has already been solved, and I can't even think of a better way to do it. You could build them into the language by having the record declaration syntax automatically create lenses instead of plain access functions. But that would make it harder to swap out the implementation, and I don't know if there's sufficient confidence in the implementations that people are ready to commit to one and build it into the compiler. It depends how much people hate the TH gunk implied by not having the derivation built in. I think a reasonable course is to use the TH gunk for now and if the world coalesces on one implementation or if everyone loves the new records and wants to enshrine them in haskell' then it gets built in. TH is good as a trying ground for new features. The thing I think *is* "broken" (well, just awkward, really), is that I have to type 'set (Config.tempo . Config.deflt)' instead of 'set (tempo.deflt)'. Once we get there, then (back to my wacky operators) 'deflt#tempo =# 42 config' is just a jumbled version of the imperative 'config.tempo := 42' only better because it can be partially applied. Then we just add a lens for Data.Map and imperative 'state[block].config.tempo := 42' can be written 'Map.lens block # config # tempo #= 42 config'... not bad! To be sure the only difference with the current situation is that you have to qualify those names.