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?
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.
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 curry
s 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)
With lenses we can also say that we are set
ing 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With