Applying functions that depend on multiple fields using the lens

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

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

What would be the most elegant way to define a function that maps (Rec abc) to (Rec (fab) (gab) c) using lens combinators?

+7
types haskell lens
source share
1 answer

The lenses

Lenses a , b and c would be written manually in terms of fmap ( <&> - crossed out infffap) as

 a :: Functor f => (A -> f A) -> Rec -> f Rec af (Rec abc) = fa <&> \a' -> Rec a' bc b :: Functor f => (B -> f B) -> Rec -> f Rec bf (Rec abc) = fb <&> \b' -> Rec ab' c c :: Functor f => (C -> f C) -> Rec -> f Rec cf (Rec abc) = fc <&> \c' -> Rec abc' 

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

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

In combination with the &&& from Control.Arrow and %~ we can write the desired function elegantly, like

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

If you really like the lens library, you can use (ab %~ (f &&& g)) instead of inAB fg .

There is no lens function for creating the lens ab from lenses a and b , since, as a rule, the product of two lenses on the same basic structure is not a lens for a product on one basic structure; both of these lenses may try to change the same main field and violate the laws of the lens.

No lenses

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

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

A function that changes both the _a and _b based on the function for each simple set of _a and _b to the results of two functions applied to the fields.

 inAB' :: (A -> B -> A) -> (A -> B -> B) -> Rec -> Rec inAB' fgr = r {_a = onAB fr, _b = onAB gr} 

Dropping a pair of curry , we get exactly the type signature you want

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

Slower, less elegant lenses

With lenses, we can also say that we are set ing a and b . This is not more elegant than using the record constructor, and it will need to build the record twice.

 inAB' :: (A -> B -> A) -> (A -> B -> B) -> Rec -> Rec inAB' fgr = set b (onAB gr) . set a (onAB fr) $ r 
+9
source share

All Articles