有没有直接的方法将多个记录字段的setter组合到一个setter?

时间:2017-03-14 11:30:51

标签: haskell lens arrows lenses

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

但这似乎是一种非常难看的手动方式。

使用镜头/箭头组合器可以直接实现吗?

2 个答案:

答案 0 :(得分:2)

镜头不是一个现成的组合器,如果组件的焦点重叠,可能会因为你可能会得到非法的定位器(或镜头)。

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

在你的情况下,手工构建setter / travers的最好的替代方法(正如你and 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}

答案 1 :(得分:1)

之前我遇到过这个问题,并且通过将它们组合成Traversal,有一个适用于两个镜头的解决方案:

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如果有人发布了更好的答案,我会非常高兴!