import Control.Lens
import Control.Lens.TH
data Foo = Foo {
_bar, _baz :: Int
}
makeLenses ''Foo
现在如果我想修改两个 int 字段,我可以这样做
barbaz :: Setter' Foo Int
barbaz = sets $ \foo f -> foo & bar %~ f
& baz %~ f
但这似乎是一种非常丑陋的手动方法。
可以直接使用镜头/箭头组合器实现同样的效果吗?
lens 并不是一个现成的组合器,大概是因为如果组件的焦点重叠,您可能会得到非法的设置器(或镜头)。
data Trio a = Trio a a a
deriving (Show)
oneTwo :: Setter' (Trio a) (a, a)
oneTwo = sets $ \f (Trio x y z) -> let (x', y') = f (x, y) in Trio x' y' z
twoThree :: Setter' (Trio a) (a, a)
twoThree = sets $ \f (Trio x y z) -> let (y', z') = f (y, z) in Trio x y' z'
cheating :: Setter' (Trio a) (a, a)
cheating = sets $ \f x -> x & oneTwo %~ f & twoThree %~ f
GHCi> Trio 1 1 1 & cheating %~ bimap (2*) (2*) & cheating %~ bimap (3+) (3+)
Trio 5 10 5
GHCi> Trio 1 1 1 & cheating %~ (bimap (2*) (2*) <&> bimap (3+) (3+))
Trio 5 13 5
就您而言,手动构建设置器/遍历的最佳替代方案(正如您和 Cristoph Hegemann 所做的那样)似乎是
liftA2 (>=>) :: ASetter' s a -> ASetter' s a -> ASetter' s a
,正如 bennofs 在其他地方建议的那样(感谢 Shersh 的链接)。如果你碰巧有一个同质对(或其他一些Bitraversable
)的透镜,你可以用both
来遍历它:
data Foo = Foo
{ _bar, _baz :: Int
} deriving (Show)
makeLenses ''Foo
barBaz :: Iso' Foo (Int, Int)
barBaz = iso ((,) <$> view bar <*> view baz) (Foo <$> fst <*> snd)
GHCi> Foo 1 2 & barBaz . both %~ (2*)
Foo {_bar = 2, _baz = 4}
Data.Data.Lens
来遍历某种类型的所有字段:
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Lens
import Data.Data.Lens
import Data.Data
data Foo = Foo
{ _bar, _baz :: Int
} deriving (Show, Data, Typeable)
makeLenses ''Foo
barBaz :: Traversal' Foo Int
barBaz = template
GHCi> Foo 1 2 & barBaz %~ (2*)
Foo {_bar = 2, _baz = 4}
我以前遇到过这个问题,并且有一个解决方案,通过将两个镜头组合成遍历来适用于两个镜头:
fields2 :: Lens' s a -> Lens' s a -> Traversal' s a
fields2 f1 f2 f s = (\v1 v2 -> s & f1 .~ v1 & f2 .~ v2) <$> f (s ^. f1) <*> f (s ^. f2)
barbaz = fields2 bar baz
这可以像这样使用:
foo & barbaz %~ f
它有点脏并且无法扩展,但它对我有用:D 如果有人发布更好的答案,我会很高兴!