[Haskell-cafe] Some thoughts on Type-Directed Name Resolution

Evan Laforge qdunkan at gmail.com
Fri Feb 10 05:31:46 CET 2012

On Thu, Feb 9, 2012 at 12:49 PM, Donn Cave <donn at avvanta.com> wrote:
> Quoth Evan Laforge <qdunkan at gmail.com>,
> ...
>> 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.

More information about the Haskell-Cafe mailing list