Подтвердить что ты не робот

Использование объектива для чтения нескольких полей

Учитывая типы

data Prisoner = P { _name   :: String
                  , _rank   :: Int
                  , _cereal :: Cereal }

data Cereal = C { _number             :: Int
                , _percentDailyValue  :: Map String Float
                , _mascot             :: String }

Я мог бы получить имя кого-то, ранга и злака с помощью сопоставления с образцом:

getNameRankAndCerealNumber_0 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_0 (P { _name=name
                                , _rank=rank
                                , _cereal = C { _number=cerealNumber }}
                             ) = (name, rank, cerealNumber)

В качестве альтернативы, я мог бы использовать линзы для извлечения каждой части отдельно

makeLenses ''Cereal
makeLenses ''Prisoner

getNameRankAndCerealNumber_1 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_1 p = (p ^. name, p ^. rank, p ^. cereal.number)

Есть ли способ извлечь все три одновременно в один обход структуры данных?

Как можно объединить Getter s, Getter s a -> Getter s b -> Getter s (a,b)?

4b9b3361

Ответ 1

Мы можем использовать экземпляр Applicative нового типа ReifiedGetter из Control.Lens.Reified:

runGetter $ (,) <$> Getter number <*> Getter mascot

В целом, новые типы в Control.Lens.Reified предлагают множество очень полезных примеров для геттеров и складок.

Примечание № 1. Обратите внимание, что мы объединяем объективы в качестве геттеров и получаем получателя взамен. Таким образом невозможно получить композитный объектив, так как возникнут проблемы, если их "фокусы" перекрываются. Каким может быть правильное поведение сеттера в этом случае?

Примечание # 2. Функция alongside позволяет объединить две линзы, получив линзу для бондайда, которая работает на двух половинки продукта. Это отличается от предыдущего, потому что мы можем быть уверены, что линзы не перекрываются. alongside пригодится, когда ваш тип является кортежем или имеет изоморфизм кортежа.

Ответ 2

Сглаживание ответа danidiaz выше, я смог построить Getter Prisoner (String, Int, Int) с помощью ReifiedGetter:

getNameRankAndCerealNumber_2 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_2 = p ^. nameRankAndCerealNumber_2

nameRankAndCerealNumber_2 :: Getter Prisoner (String, Int, Int)
nameRankAndCerealNumber_2 = runGetter ((,,) <$> Getter name <*> Getter rank <*> Getter (cereal.number))

И Lens' Prisoner (String, Int, Int) с помощью alongside, хотя мне пришлось вручную построить Iso' между Prisoner и HList [String, Int, Int] и между HList [a,b,c] и (a,b,c).

getNameRankAndCerealNumber_3 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_3 p = p ^. nameRankAndCerealNumber_3

setNameRankAndCerealNumber_3 :: (String, Int, Int) -> Prisoner -> Prisoner
setNameRankAndCerealNumber_3 t p = p & nameRankAndCerealNumber_3 .~ t

nameRankAndCerealNumber_3 :: Lens' Prisoner (String, Int, Int)
nameRankAndCerealNumber_3 = hlist . alongside id (alongside id number) . triple
  where triple :: Iso' (a,(b,c)) (a,b,c)
        triple = dimap (\(a,(b,c)) -> (a,b,c)) (fmap $ \(a,b,c) -> (a,(b,c)))
        hlist :: Iso' Prisoner (String, (Int, Cereal))
        hlist = dimap (\(P n r c) -> (n,(r,c))) (fmap $ \(n,(r,c)) -> P n r c)

Там может быть более простой способ сделать это, но это еще один вопрос.