[Haskell-cafe] Re: is there a more concise way to generate helper functions for a datatype built on records?

Benedikt Huber benjovi at gmx.net
Wed Nov 28 06:24:12 EST 2007

apfelmus schrieb:
> Benedikt Huber wrote:
>> So, the Ref deriviation is really nice for sequential updates;
>> parallel updates on the other hand need some work.
>> ..
>> While the select part of the Ref is expressed using &&&, I don't know
>> how the
>> parallel update can be expressed in terms of combinators. Any hints ?
> You can't do that, and for good reason! While
>   players :: Lens Game (Player,Player)
> is entirely fine since  Game ~ (Player,Player,Object2D), there cannot be
> a general parallel combinator
>   (&&&) :: Lens a b -> Lens a c -> Lens a (b,c)
Thanks for the nice overview.
I see there can't be a general purpose combinator (&&&) for lenses.
One could still define a helper function though:

combineDisjoint :: Lens a b -> Lens a c -> Lens a (b,c)
combineDisjoint l1 l2 = Lens $ select &&& update
     select = (fst . focus l1) &&& (fst . focus l2)
     update cx (a,b) = flip (snd . focus l2) b $ (snd . focus l1) cx a

which performs the first update using the initial context, and the
second one using the updated context. Just to have a simple way of
defining lensPlayers in term of lensPlayer1 `combineDisjoint`
lensPlayer2. While it is not a (general purpose) combinator, it is still
helpfull for combining lenses focusing on fields of a record.


I just want to rephrase my question about paralell updates; it has more
to do with records updates than with References / Lenses, though:

Suppose we have a record

data R = R { a:: A, b :: B, c :: C } deriving (Show {-! Ref !-})

and update functions

fa :: a -> a, fb :: b -> b, fc :: c -> c

Now the standard way to perform the update on R would be

updateR = \r@(R {a=a,b=b,c=c}) -> r { a = fa a,b = fb b,c = fc c }

which is 'a little bit' cumbersome.

With update deriviations (like Update using DrIFT), references (Ref 
using Data.Derive) or lenses it is easy to perform the update 
sequentially (using DrIFT style for simplicity):

updateR' = a_u fa . b_u fb . c_u fc

But this corresponds to

updateR' = (\f r -> r { a = f (a r) }) fa .
            (\f r -> r { b = f (b r) }) fb .
            (\f r -> r { c = f (c r) }) fc

which (in some way) is not 'the same' as updateR.

First, I (uneducatedly) guess that the record updates cannot be 
'executed' in paralell, i.e. the record has to be deconstructed and 
build up again three times.
And second, neither the types of the updates (e.g. a_u fa :: R -> R) nor 
the structure of updateR' (composing R->R functions) do reflect the fact
that actually disjoint fields of the record are updated.

Now I know there are great record proposals (which extend the haskell
language or use some type hackery), but I wondered if there is also a
solution which can be used right now and which works fine with the
standard record types.

I'll give a naive ad-hoc example to illustrate the idea.
One could automatically derive the following data type and the
associated functions for R:

data R_upd = R_upd { updA :: A -> A, updB :: B -> B, updC :: C -> C }
rUpd = R_upd id id id
updR :: R_upd -> R -> R
updR rupd r@(R { a=a,b=b,c=c }) = r
   { a = (updA rupd a),
     b = (updB rupd b),
     c = (updC rupd c) }

which would allow to write things like

updGame $
   gameUpdate { updPlayer1 = increaseScore, updPlayer2 = decreaseScore })

Though simple, I hope it is possible to understand the idea (I know 
there is a lot of namespace pollution).
And of course, someone has thought of something much more sophistacted
already :)
What are the drawbacks of such an approach ?


More information about the Haskell-Cafe mailing list