使用镜头应用依赖于多个字段的函数
Applying functions that depend on multiple fields using the lens
设A, B, C
为类型,有两个函数f :: (A , B) -> A
和g :: (A , B) -> B
。考虑以下记录类型
data Rec = Rec{_a :: A, _b :: B, _c :: C}
。
使用 lens
组合器定义将 (Rec a b c)
映射到 (Rec (f a b) (g a b) c)
的函数的最优雅方法是什么?
镜片
镜头a
、b
和c
将根据fmap
手写出来(<&>
是中缀翻转fmap)作为
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
and %~
中的&&&
我们可以优雅地写出想要的函数
inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = ab %~ (f &&& g)
如果您对镜头库非常满意,您可能更愿意使用 (ab %~ (f &&& g))
而不是 inAB f g
。
有 isn't a lens 从透镜 a
和 b
构建透镜 ab
的函数,因为通常两个透镜在相同底层结构上的乘积不是一个用于产品的透镜到一个底层结构上;两个镜头都可能试图改变相同的基础场并违反镜头定律。
没有镜头
没有镜头,您可以定义一个函数来将函数应用于记录的 _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
s 我们得到你想要的类型签名
inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = inAB' (curry f) (curry g)
速度较慢,镜头不够优雅
对于镜头,我们也可以说我们正在 set
ing 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
设A, B, C
为类型,有两个函数f :: (A , B) -> A
和g :: (A , B) -> B
。考虑以下记录类型
data Rec = Rec{_a :: A, _b :: B, _c :: C}
。
使用 lens
组合器定义将 (Rec a b c)
映射到 (Rec (f a b) (g a b) c)
的函数的最优雅方法是什么?
镜片
镜头a
、b
和c
将根据fmap
手写出来(<&>
是中缀翻转fmap)作为
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
and %~
中的&&&
我们可以优雅地写出想要的函数
inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = ab %~ (f &&& g)
如果您对镜头库非常满意,您可能更愿意使用 (ab %~ (f &&& g))
而不是 inAB f g
。
有 isn't a lens 从透镜 a
和 b
构建透镜 ab
的函数,因为通常两个透镜在相同底层结构上的乘积不是一个用于产品的透镜到一个底层结构上;两个镜头都可能试图改变相同的基础场并违反镜头定律。
没有镜头
没有镜头,您可以定义一个函数来将函数应用于记录的 _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
s 我们得到你想要的类型签名
inAB :: ((A, B) -> A) -> ((A, B) -> B) -> Rec -> Rec
inAB f g = inAB' (curry f) (curry g)
速度较慢,镜头不够优雅
对于镜头,我们也可以说我们正在 set
ing 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