7

Let A, B, C be types and there are two functions f :: (A , B) -> A and g :: (A , B) -> B. Consider following record type

data Rec = Rec{_a :: A, _b :: B, _c :: C}.

What would be the most elegant way to define function that maps (Rec a b c) to (Rec (f a b) (g a b) c) using the lens combinators?

Katty J.
  • 686
  • 4
  • 11

1 Answers1

9

Lenses

The lenses a, b, and c would be written out by hand in terms of fmap (<&> is infix flipped fmap) as

a :: Functor f => (A -> f A) -> Rec -> f Rec
a f (Rec a b c) = f a <&> \a' -> Rec a' b c

b :: Functor f => (B -> f B) -> Rec -> f Rec
b f (Rec a b c) = f b <&> \b' -> Rec a b' c

c :: Functor f => (C -> f C) -> Rec -> f Rec
c f (Rec a b c) = f c <&> \c' -> Rec a b c'

As cchalmers points out, we can extend this pattern to write a lens for both the _a and _b fields at the same time

ab :: Functor f => ((A, B) -> f (A, B)) -> Rec -> f Ref
ab f (Rec a b c) = f (a,b) <&> \(a',b') -> Rec a' b' c

Combined with &&& from Control.Arrow and %~ we can write the desired function elegantly as

inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = ab %~ (f &&& g)

If you are very comfortable with the lens library you might prefer to use (ab %~ (f &&& g)) instead of inAB f g.

There isn't a lens function for building the lens ab from the lenses a and b since in general the product of two lenses onto the same underlying structure is not a lens for the product onto the one underlying structure; both of the two lenses might try to change the same underlying field and violate the lens laws.

No lenses

Without lenses, you can define a function to apply a function to the _a and _b fields of the record.

onAB :: (A -> B -> c) -> Rec -> c
onAB f r = f (_a r) (_b r)

A function that modifies both the _a and _b fields based on a function for each just sets _a and _b to the results of the two functions applied to the fields.

inAB' :: (A -> B -> A) -> (A -> B -> B) -> Rec -> Rec
inAB' f g r = r {_a = onAB f r, _b = onAB g r}

Tossing in a couple currys we get exactly the type signature you want

inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = inAB' (curry f) (curry g)

Slower, less elegant lenses

With lenses we can also say that we are seting a and b. It's not any more elegant than using the record constructor and it will need to construct the record twice.

inAB' :: (A -> B -> A) -> (A -> B -> B) -> Rec -> Rec
inAB' f g r = set b (onAB g r) . set a (onAB f r) $ r
Community
  • 1
  • 1
Cirdec
  • 24,019
  • 2
  • 50
  • 100
  • 2
    Two questions discussing the problem you mention at the end: http://stackoverflow.com/q/17528119 , http://stackoverflow.com/q/23321844 . In this specific case the relevant lens is legal, and so in principle it would be fine to define it from scratch, though it probably wouldn't be worth the trouble. – duplode Sep 16 '15 at 22:39
  • 2
    Manually writing it isn't that bad: `_ab f (Rec a b c) = f (a,b) <&> \(a',b') -> Rec a' b' c`. Then the function is simply `_ab %~ \(a,b) -> (f a b, g a b)`. – cchalmers Sep 16 '15 at 22:42
  • @cchalmers It isn't indeed; my "not worth the trouble" was only relative to the lens-less solution, and assuming the composed lens won't prove useful elsewhere. – duplode Sep 16 '15 at 22:50