Вы правы в том, что можете написать это с помощью outside
.Для начала некоторые определения:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
newtype Foo = Foo { _fooName :: String }
deriving (Eq, Ord, Show)
makeLenses ''Foo
newtype Bar = Bar { _barName :: String }
deriving (Eq, Ord, Show)
makeLenses ''Bar
newtype Baz = Baz { _bazName :: String }
deriving (Eq, Ord, Show)
makeLenses ''Baz
data Problem =
ProblemFoo Foo |
ProblemBar Bar |
ProblemBaz Baz
deriving (Eq, Ord, Show)
makePrisms ''Problem
Выше приведено только то, что вы описали в своем вопросе, за исключением того, что я также делаю призмы для Problem
.
Тип *Для ясности 1008 * (специально для функций, простых линз и простых призм):
outside :: Prism' s a -> Lens' (s -> r) (a -> r)
При наличии призмы, например, для случая типа суммы, outside
дает вам линзуна функции из типа sum, которая нацелена на ветвь функции, которая обрабатывает регистр.Указание всех ветвей функции равнозначно обработке всех случаев:
problemName :: Problem -> String
problemName = error "Unhandled case in problemName"
& outside _ProblemFoo .~ view fooName
& outside _ProblemBar .~ view barName
& outside _ProblemBaz .~ view bazName
Это довольно красиво, за исключением необходимости бросать в случае error
из-за отсутствия разумного значения по умолчанию. Библиотека total предлагает альтернативу, которая улучшит это и обеспечит полную проверку на всем пути, если вы захотите немного исказить типы:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
import Control.Lens
import GHC.Generics (Generic)
import Lens.Family.Total
-- etc.
-- This is needed for total's exhaustiveness check.
data Problem_ a b c =
ProblemFoo a |
ProblemBar b |
ProblemBaz c
deriving (Generic, Eq, Ord, Show)
makePrisms ''Problem_
instance (Empty a, Empty b, Empty c) => Empty (Problem_ a b c)
type Problem = Problem_ Foo Bar Baz
problemName :: Problem -> String
problemName = _case
& on _ProblemFoo (view fooName)
& on _ProblemBar (view barName)
& on _ProblemBaz (view bazName)