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

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

Nun, wenn ich beide int Felder ändern mag, kann ichGibt es eine direkte Möglichkeit, Setter für mehrere Datensatzfelder zu einem einzigen Setter zu kombinieren?

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

tun, aber dies scheint wie eine ziemlich hässlich manuelle Art und Weise, es zu tun.

Kann das gleiche direkt mit Objektiv/Pfeil-Kombinatoren erreicht werden?

+0

Dies scheint Ihre Frage zu beantworten: http://stackoverflow.com/questions/17528119/combining-lenses – Shersh

+0

Wenn Sie ein Tupel anstelle von 'Foo' verwenden, könnten Sie etwas wie [' (1,2) und beide tun. ~ 10'] (https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-Traversal.html#v:both) um beide Elemente des Tupels auf '10' zu setzen. – Alec

Antwort

1

Ich habe dieses Problem hatte vor und eine Lösung, die durch die Kombination von ihnen in eine Traversal für zwei Linsen funktioniert:

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 

Dies kann als wie verwendet werden:

foo & barbaz %~ f 

Es ist ein etwas dreckig und skaliert nicht, aber es funktioniert für mich: D Wenn jemand eine schönere Antwort postet, werde ich sehr glücklich sein!

2

Objektiv ist kein fertiger Kombinator dafür, vermutlich weil man illegale Setzer (oder Linsen) bekommen kann, wenn sich die Fokusse der Komponenten überlappen.

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 

In Ihrem Fall die schönste Alternative der Setter/Traversal von Hand zu bauen (wie Sie and Cristoph Hegemann tun werden) scheint liftA2 (>=>) :: ASetter' s a -> ASetter' s a -> ASetter' s a zu sein, wie bennofs (dank Shersh für den Link an anderer Stelle vorgeschlagen). Wenn Sie ein Objektiv auf ein homogenes Paar haben zufällig (oder eine andere Bitraversable) herumliegen, können Sie die Traversal aus ihm heraus mit both erhalten:

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} 

Eine weitere Möglichkeit ist die Nutzung Data.Data.Lens zu erhalten eine Traversal aller Felder eines bestimmten Typs:

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