
On Thu, Feb 9, 2012 at 12:49 PM, Donn Cave
Quoth Evan Laforge
, ... The non-composing non-abstract updates are what bug me, and make me scatter about tons of 'modifyThis' functions, both for composability and to protect from field renames.
So ... at the risk of stating the obvious, is it fair to say the root of this problem is at least the lack of `first class' update syntax?
I think there are two problems, or at least the way I'm thinking about it I'm decomposing it into two parts. One is the lack of first class and composable update, but that is solved satisfactorily by lenses. The second is how to write those composable first class names without getting RSI. So at least the way I'm thinking currently, only the second needs to be solved. Module qualified names work, but are wordy. Importing unqualified leads to clashes. Typeclasses can solve that, but are global so they're kind of "too" unqualified---no export control. So by that logic, we need either export control for typeclasses or some other kind of automatic resolution which is not global, like my #suggestion. Both would be orthogonal and interesting features in their own right, but now that I think of it maybe export control for typeclasses or closed typeclasses might fit in better. I know a lot of people have wanted those though, so maybe there are serious snags.
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. I've done this before: data Event = Event { event_string :: String, ... } -- oops, strings are inefficient, but Event is already used in many places -- most of which enjoy the convenience of Strings and are not in hotspots: data Event = Event { event_text :: Text, ...} event_string = Text.unpack . event_text With lenses you can do this for update as well: event_string = lens (Text.unpack . event_text) (\s e -> e { event_text = Text.pack s }) You can also enforce invariants, etc. It would be a shame to have a nice record update syntax only to be discouraged from using it because it would tie you too tightly to the current shape of the data structure. There would always be a tension and every time I wrote down a new type I'd waste some time thinking: is the record big enough to want to define functions, or can I get away with direct access? Of course it may *get* bigger later so... It's the same tension as direct access vs. accessors in the OO world, I guess.