@@ -19,20 +19,9 @@
19-- Stability : experimental
19-- Stability : experimental
20-- Portability : non-portable
20-- Portability : non-portable
22−-- Structural subtyping
22+-- Structural subtype relationship between record types.
24−-----------------------------------------------------------------------------
24+-- The running example in this module is the following two types:
25−module Data.Generic.Record.Subtype
29−import Data.Generic.Record.HasField
30−import Data.Generic.Internal.Lens
32−import Data.Kind (Type)
35−-- |Structural subtype relationship
@@ -57,30 +46,93 @@ import GHC.Generics
49+-----------------------------------------------------------------------------
50+module Data.Generic.Record.Subtype
55+import Data.Generic.Record.HasField
56+import Data.Generic.Record.Internal.Contains
58+import Data.Generic.Internal.Lens
60+import Data.Kind (Type)
63+-- |Structural subtype relationship
61−-- >>> human
65+-- @sub@ is a (structural) `subtype' of @sup@, if its fields are a subset of
62−-- Human {name = "Tunyasz", age = 50, address = "London"}
66+-- those of @sup@.
64−-- >>> upcast human :: Animal
65−-- Animal {name = "Tunyasz", age = 50}
67class Subtype sub sup where
68class Subtype sub sup where
68 -- | Cast the more specific subtype to the more general supertype
69 -- | Cast the more specific subtype to the more general supertype
71+ -- >>> upcast human :: Animal
72+ -- Animal {name = "Tunyasz", age = 50}
69 upcast :: sub -> sup
74 upcast :: sub -> sup
75+ -- | Plug a smaller structure into a larger one
77+ -- >>> smash (Animal "dog" 10) human
78+ -- Human {name = "dog", age = 10, address = "London"}
79+ smash :: sup -> sub -> sub
71-- | Instances are created by the compiler
81-- | Instances are created by the compiler
72−instance (Convert (Rep a) (Rep b), Generic a, Generic b) => Subtype a b where
82+instance (GSmash (Rep a) (Rep b), GUpcast (Rep a) (Rep b), Generic a, Generic b) => Subtype a b where
73− upcast = to . convert . from
83+ upcast = to . gupcast . from
84+ smash p b = to $ gsmash (from p) (from b)
86+-- | Structural subtype lens. Given a subtype relationship @sub :< sup@,
87+-- we can focus on the @sub@ structure of @sup@.
89+-- >>> human ^. subtype @Animal
90+-- Animal {name = "Tunyasz", age = 50}
92+-- >>> set (subtype @Animal) (Animal "dog" 10) human
93+-- Human {name = "dog", age = 10, address = "London"}
94+subtype :: forall sup sub. Subtype sub sup => Lens' sub sup
95+subtype f sub = fmap (flip smash sub) (f (upcast sub))
97+--------------------------------------------------------------------------------
98+-- * Generic upcasting
100+-- | Upcast 'sub to 'sup' (generic rep)
101+class GUpcast (sub :: Type -> Type) (sup :: Type -> Type) where
102+ gupcast :: sub p -> sup p
104+instance (GUpcast sub a, GUpcast sub b) => GUpcast sub (a :*: b) where
105+ gupcast rep = gupcast rep :*: gupcast rep
107+instance {-# OVERLAPPING #-} GHasField field sub t => GUpcast sub (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) where
108+ gupcast r = M1 (K1 (r ^. glabel @field))
110+instance GUpcast sub sup => GUpcast sub (M1 i c sup) where
111+ gupcast = M1 . gupcast
113+--------------------------------------------------------------------------------
114+-- * Generic smashing
116+class GSmash sub sup where
117+ gsmash :: sup p -> sub p -> sub p
119+instance (GSmash a sup, GSmash b sup) => GSmash (a :*: b) sup where
120+ gsmash rep (a :*: b) = gsmash rep a :*: gsmash rep b
122+instance {-# OVERLAPPING #-}
123+ ( leaf ~ (S1 ('MetaSel ('Just field) p f b) t)
124+ , GSmashLeaf leaf sup (Contains field sup)
125+ ) => GSmash (S1 ('MetaSel ('Just field) p f b) t) sup where
126+ gsmash = gsmashLeaf @_ @_ @(Contains field sup)
75−-- | Convert 'rep' into 'f'
128+instance GSmash sub sup => GSmash (M1 i c sub) sup where
76−class Convert (rep :: Type -> Type) (f :: Type -> Type) where
129+ gsmash sup (M1 sub) = M1 (gsmash sup sub)
77− convert :: rep p -> f p
79−instance (Convert rep a, Convert rep b) => Convert rep (a :*: b) where
131+class GSmashLeaf sub sup (w :: Maybe Type) where
80− convert rep = convert rep :*: convert rep
132+ gsmashLeaf :: sup p -> sub p -> sub p
82−instance {-# OVERLAPPING #-} GHasField field rep t => Convert rep (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) where
134+instance (GHasField field sup t) => GSmashLeaf (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) sup ('Just t) where
83− convert rep = M1 (K1 (rep ^. glabel @field))
135+ gsmashLeaf sup (M1 (K1 _)) = M1 (K1 (sup ^. glabel @field))
85−instance Convert rep f => Convert rep (M1 i c f) where
137+instance GSmashLeaf (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) sup 'Nothing where
86− convert = M1 . convert
138+ gsmashLeaf _ = id