让A, B, C
成为类型,有两个函数f :: (A , B) -> A
和g :: (A , B) -> B
。请考虑以下记录类型
data Rec = Rec{_a :: A, _b :: B, _c :: C}
。
使用(Rec a b c)
组合器定义将(Rec (f a b) (g a b) c)
映射到lens
的函数的最优雅方法是什么?
答案 0 :(得分:9)
镜头a
,b
和c
将以fmap
(<&>
为中缀翻转fmap)手写为< / p>
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'
正如cchalmers所指出的那样,我们可以扩展这种模式,同时为_a
和_b
字段写一个镜头
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
结合Control.Arrow
和%~
中的&&&
,我们可以优雅地编写所需的功能
inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = ab %~ (f &&& g)
如果您对镜头库感到满意,可能更倾向于使用(ab %~ (f &&& g))
代替inAB f g
。
有isn't lens功能,用于从镜头ab
和a
构建镜头b
,因为一般来说,两个镜头的产品在同一镜头上底层结构不是产品在一个底层结构上的透镜;两个镜头都可能试图改变相同的底层场并违反镜头规律。
如果没有镜头,您可以定义一个函数,将函数应用于记录的_a
和_b
字段。
onAB :: (A -> B -> c) -> Rec -> c
onAB f r = f (_a r) (_b r)
根据每个函数修改_a
和_b
字段的函数,只需将_a
和_b
设置为应用于该函数的两个函数的结果。字段。
inAB' :: (A -> B -> A) -> (A -> B -> B) -> Rec -> Rec
inAB' f g r = r {_a = onAB f r, _b = onAB g r}
折腾了几个curry
,我们得到了你想要的类型签名
inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = inAB' (curry f) (curry g)
使用镜头我们也可以说set
a
和b
。它不比使用记录构造函数更优雅,它需要构建两次记录。
inAB' :: (A -> B -> A) -> (A -> B -> B) -> Rec -> Rec
inAB' f g r = set b (onAB g r) . set a (onAB f r) $ r