2014-11-03 5 views
18

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

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)?

+0

Вы имеете в виду «серийный номер» или эта шутка, которую я не получаю? –

+2

Том Эллис: Это просто плохой каламбур. – rampion

+0

моим оригинальным вдохновением было [использование сопоставления шаблонов, которое этот запрос тяги пытается выполнить с помощью ghc 7.8. *] (Https://github.com/schell/hdevtools/pull/1). Если код использовал полевые экстракторы или линзы, это не требовалось бы исправлять. – rampion

ответ

22

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

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

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

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

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

+1

Спасибо! Надеюсь, вы не против, я немного уточнил ваш ответ в этом вопросе. – rampion

4

конкретизации danidiaz's answer above, я смог построить 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) 

Возможно, это будет более простой способ, но это другой вопрос.