Использование объектива для чтения нескольких полей
Учитывая типы
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)
?
Ответы
Ответ 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)
Там может быть более простой способ сделать это, но это еще один вопрос.