Что касается Control.Lens.TH
, инструмент, ближайший к тому, что вам нужно, это makeClassy
:
data Small = Small
{ _name :: Text
-- ...probably at least a few fields more
}
deriving Show
makeClassy ''Small
data Big = Big
{ _bigSmall :: Small
, _bigEmail :: Text
-- ...possibly many more fields
}
deriving Show
makeClassy ''Big -- As far as this demo goes, not really necessary.
instance HasSmall Big where
small = bigSmall
Этот подход требует, чтобы в поле Small
было Big
, так что доступ к полям в Small
можно направить через сгенерированный класс HasSmall
:
GHCi> :info HasSmall
class HasSmall c where
small :: Lens' c Small
name :: Lens' c Text
{-# MINIMAL small #-}
-- Defined at Test.hs:16:1
instance HasSmall Small -- Defined at Test.hs:16:1
instance HasSmall Big -- Defined at Test.hs:27:10
GHCi> :set -XTypeApplications
GHCi> :t name @Big
name @Big :: Functor f => (Text -> f Text) -> Big -> f Big
Другой подход будет заключаться в абстрагировании полей через makeFields
:
data Small = Small
{ _smallName :: Text
-- ...probably at least a few fields more
}
deriving Show
makeFields ''Small
data Big = Big
{ _bigName :: Text
, _bigEmail :: Text
-- ...possibly many more fields
}
deriving Show
makeFields ''Big
GHCi> :info HasName
class HasName s a | s -> a where
name :: Lens' s a
{-# MINIMAL name #-}
-- Defined at Test2.hs:16:1
instance HasName Small Text -- Defined at Test2.hs:16:1
instance HasName Big Text -- Defined at Test2.hs:25:1
GHCi> :t name @Big
name @Big :: Functor f => (Text -> f Text) -> Big -> f Big
Один потенциальный недостаток makeFields
в этом случае использования состоит в том, что, как вы заметили, механизм оставляет его полностью открытым, какие типы могут быть переданы полям. (Напротив, определение Small
в примере makeClassy
косвенно указывает, что любые объективы name
будут иметь цели типа Text
.)