Есть ли прямой способ объединить сеттеры для нескольких полей записи с одним сеттером?

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

но это кажется довольно уродливым ручным способом сделать это.

Можно ли добиться того же непосредственно с помощью комбинаторов объектив/стрелка?


person leftaroundabout    schedule 14.03.2017    source источник
comment
Кажется, это отвечает на ваш вопрос: stackoverflow.com/questions/17528119/combining-lenses   -  person Shersh    schedule 14.03.2017
comment
Если бы вы использовали кортеж вместо Foo, вы могли бы сделать что-то вроде (1,2) & both .~ 10, чтобы установить для обоих элементов кортежа значение 10.   -  person Alec    schedule 14.03.2017
comment
Вы можете комбинировать сеттеры, такие как bar %~ f, используя compose   -  person Janus Troelsen    schedule 25.11.2020


Ответы (2)


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

В вашем случае кажется, что лучшая альтернатива созданию сеттера/обхода вручную (как вы делаете и Кристоф Хегеманн) be 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}
person duplode    schedule 14.03.2017

У меня была эта проблема раньше, и у меня есть решение, которое работает для двух линз, объединив их в 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 Если кто-то опубликует более приятный ответ, я буду очень рад!

person Christoph Hegemann    schedule 14.03.2017