2017-03-14 5 views
6
import Control.Lens 
import Control.Lens.TH 

data Foo = Foo { 
    _bar, _baz :: Int 
    } 
makeLenses ''Foo 

Maintenant, si je veux modifier à la fois int champs, je peux faireExiste-t-il un moyen direct de combiner des setters pour plusieurs champs d'enregistrement avec un seul setter?

barbaz :: Setter' Foo Int 
barbaz = sets $ \foo f -> foo & bar %~ f 
           & baz %~ f 

mais cela semble être un moyen manuel assez laid pour le faire.

Est-ce que cela peut être réalisé directement avec des combinateurs d'objectif/flèche?

+0

Cela semble répondre à votre question: http://stackoverflow.com/questions/17528119/combined-lenses – Shersh

+0

Si vous utilisiez un tuple au lieu de 'Foo', vous pourriez faire quelque chose comme [' (1,2) et les deux. ~ 10'] (https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-Traversal.html#v:both) pour mettre les deux éléments du tuple à '10'. – Alec

Répondre

1

J'ai eu ce problème avant et avoir une solution qui fonctionne pour deux lentilles en les combinant dans un 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 

Cela peut que être utilisé comme:

foo & barbaz %~ f 

C'est peu sale et pas à l'échelle, mais ça marche pour moi: D Si quelqu'un affiche une réponse plus agréable, je serai très heureux!

2

objectif ne fait pas un combinateur ready-made pour cela, probablement parce que vous pouvez obtenir des setters illégaux (ou des lentilles) si les foyers des composants se chevauchent.

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 

Dans votre cas, la plus belle alternative à la construction du compositeur/traversal à la main (comme vous and Cristoph Hegemann faites) semble être liftA2 (>=>) :: ASetter' s a -> ASetter' s a -> ASetter' s a, comme suggéré par ailleurs par bennofs (grâce Shersh pour le lien). Si vous arrive d'avoir une lentille à une paire homogène (ou un autre Bitraversable) qui traînent, vous pouvez obtenir le traversal sortir avec 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} 

Une autre possibilité est d'exploiter Data.Data.Lens à obtenir un traversal de tous les champs d'un certain type:

{-# 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}