on master help sign in
Structural subtype lens
commitd3e8458f6f771befa0e6dd7535adf1f2b2f389be authorCsongor Kiss <kiss.csongor.kiss@gmail.com> dateSat 10 Jun 2017, 15:17 (9 years ago) parent31d1455
5 files changed+144−60
generic-records.cabal+2−1 src/Data/Generic/Internal/Lens.hs+1−1 src/Data/Generic/Record/HasField.hs+3−29 src/Data/Generic/Record/Internal/Contains.hs+57 src/Data/Generic/Record/Subtype.hs+81−29
generic-records.cabal+2−1
@@ -32,8 +32,9 @@ library
3232 exposed-modules: Data.Generic.Record
3333 , Data.Generic.Record.Subtype
3434 , Data.Generic.Record.HasField
35+ , Data.Generic.Internal.Lens
3536
36− other-modules: Data.Generic.Internal.Lens
37+ other-modules: Data.Generic.Record.Internal.Contains
3738
3839 other-extensions: AllowAmbiguousTypes
3940 , DataKinds
src/Data/Generic/Internal/Lens.hs+1−1
@@ -10,7 +10,7 @@
1010-- Stability : experimental
1111-- Portability : non-portable
1212--
13−-- Unexported lens helpers
13+-- Internal lens helpers. Only exported for Haddock
1414--
1515-----------------------------------------------------------------------------
1616module Data.Generic.Internal.Lens where
src/Data/Generic/Record/HasField.hs+3−29
@@ -34,9 +34,11 @@ module Data.Generic.Record.HasField
3434 , GHasField (..)
3535 ) where
3636
37+import Data.Generic.Record.Internal.Contains
38+
3739import Data.Generic.Internal.Lens
3840
39−import GHC.TypeLits (Symbol, TypeError, ErrorMessage(..))
41+import GHC.TypeLits (Symbol)
4042import Data.Kind (Type)
4143import GHC.Generics
4244
@@ -109,34 +111,6 @@ instance (GHasField field g ret) => GHasFieldProd field f g ret 'Nothing where
109111
110112--------------------------------------------------------------------------------
111113
112−-- | Look up a record field by name in the generic representation, and return
113−-- its corresponding type, if exists.
114−type family Contains (field :: Symbol) f :: Maybe Type where
115− Contains field (S1 ('MetaSel ('Just field) _ _ _) (Rec0 t))
116− = 'Just t
117− Contains field (f :*: g)
118− = Contains field f <|> Contains field g
119− Contains field (S1 _ _)
120− = 'Nothing
121− Contains field (C1 m f)
122− = Contains field f
123− Contains field (D1 m f)
124− = Contains field f
125− Contains field (Rec0 _)
126− = 'Nothing
127− Contains field U1
128− = 'Nothing
129− Contains field V1
130− = 'Nothing
131− Contains x t = TypeError ('ShowType t)
132−
133−-- | Type-level alternative
134−type family (a :: Maybe k) <|> (b :: Maybe k) :: Maybe k where
135− 'Just x <|> _ = 'Just x
136− _ <|> b = b
137−
138−--------------------------------------------------------------------------------
139−
140114-- | Like 'HasField', but on the generic representation
141115class GHasField (field :: Symbol) (s :: Type -> Type) a | field s -> a where
142116 glabel :: Lens' (s x) a
src/Data/Generic/Record/Internal/Contains.hsnew file+57
@@ -0,0 +1,57 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE FunctionalDependencies #-}
5+{-# LANGUAGE PolyKinds #-}
6+{-# LANGUAGE Rank2Types #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE TypeApplications #-}
9+{-# LANGUAGE TypeFamilies #-}
10+{-# LANGUAGE TypeOperators #-}
11+{-# LANGUAGE UndecidableInstances #-}
12+
13+-----------------------------------------------------------------------------
14+-- |
15+-- Module : Data.Generic.Record.Internal.Contains
16+-- Copyright : (C) 2017 Csongor Kiss
17+-- License : BSD3
18+-- Maintainer : Csongor Kiss <kiss.csongor.kiss@gmail.com>
19+-- Stability : experimental
20+-- Portability : non-portable
21+--
22+-- Look up fields in the generic representation
23+--
24+-----------------------------------------------------------------------------
25+module Data.Generic.Record.Internal.Contains where
26+
27+import Data.Kind (Type)
28+import GHC.Generics
29+
30+import GHC.TypeLits
31+
32+-- | Look up a record field by name in the generic representation, and return
33+-- its corresponding type, if exists.
34+type family Contains (field :: Symbol) f :: Maybe Type where
35+ Contains field (S1 ('MetaSel ('Just field) _ _ _) (Rec0 t))
36+ = 'Just t
37+ Contains field (f :*: g)
38+ = Contains field f <|> Contains field g
39+ Contains field (S1 _ _)
40+ = 'Nothing
41+ Contains field (C1 m f)
42+ = Contains field f
43+ Contains field (D1 m f)
44+ = Contains field f
45+ Contains field (Rec0 _)
46+ = 'Nothing
47+ Contains field U1
48+ = 'Nothing
49+ Contains field V1
50+ = 'Nothing
51+ Contains x t = TypeError ('ShowType t)
52+
53+-- | Type-level alternative
54+type family (a :: Maybe k) <|> (b :: Maybe k) :: Maybe k where
55+ 'Just x <|> _ = 'Just x
56+ _ <|> b = b
57+
src/Data/Generic/Record/Subtype.hs+81−29
@@ -19,20 +19,9 @@
1919-- Stability : experimental
2020-- Portability : non-portable
2121--
22−-- Structural subtyping
22+-- Structural subtype relationship between record types.
2323--
24−-----------------------------------------------------------------------------
25−module Data.Generic.Record.Subtype
26− ( Subtype (..)
27− ) where
28−
29−import Data.Generic.Record.HasField
30−import Data.Generic.Internal.Lens
31−
32−import Data.Kind (Type)
33−import GHC.Generics
34−
35−-- |Structural subtype relationship
24+-- The running example in this module is the following two types:
3625--
3726-- @
3827--
@@ -57,30 +46,93 @@ import GHC.Generics
5746--
5847-- @
5948--
49+-----------------------------------------------------------------------------
50+module Data.Generic.Record.Subtype
51+ ( Subtype (..)
52+ , subtype
53+ ) where
54+
55+import Data.Generic.Record.HasField
56+import Data.Generic.Record.Internal.Contains
57+
58+import Data.Generic.Internal.Lens
59+
60+import Data.Kind (Type)
61+import GHC.Generics
62+
63+-- |Structural subtype relationship
6064--
61−-- >>> human
62−-- Human {name = "Tunyasz", age = 50, address = "London"}
63−--
64−-- >>> upcast human :: Animal
65−-- Animal {name = "Tunyasz", age = 50}
65+-- @sub@ is a (structural) `subtype' of @sup@, if its fields are a subset of
66+-- those of @sup@.
6667--
6768class Subtype sub sup where
6869 -- | Cast the more specific subtype to the more general supertype
70+ --
71+ -- >>> upcast human :: Animal
72+ -- Animal {name = "Tunyasz", age = 50}
73+ --
6974 upcast :: sub -> sup
75+ -- | Plug a smaller structure into a larger one
76+ --
77+ -- >>> smash (Animal "dog" 10) human
78+ -- Human {name = "dog", age = 10, address = "London"}
79+ smash :: sup -> sub -> sub
7080
7181-- | Instances are created by the compiler
72−instance (Convert (Rep a) (Rep b), Generic a, Generic b) => Subtype a b where
73− upcast = to . convert . from
82+instance (GSmash (Rep a) (Rep b), GUpcast (Rep a) (Rep b), Generic a, Generic b) => Subtype a b where
83+ upcast = to . gupcast . from
84+ smash p b = to $ gsmash (from p) (from b)
85+
86+-- | Structural subtype lens. Given a subtype relationship @sub :< sup@,
87+-- we can focus on the @sub@ structure of @sup@.
88+--
89+-- >>> human ^. subtype @Animal
90+-- Animal {name = "Tunyasz", age = 50}
91+--
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))
96+
97+--------------------------------------------------------------------------------
98+-- * Generic upcasting
99+
100+-- | Upcast 'sub to 'sup' (generic rep)
101+class GUpcast (sub :: Type -> Type) (sup :: Type -> Type) where
102+ gupcast :: sub p -> sup p
103+
104+instance (GUpcast sub a, GUpcast sub b) => GUpcast sub (a :*: b) where
105+ gupcast rep = gupcast rep :*: gupcast rep
106+
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))
109+
110+instance GUpcast sub sup => GUpcast sub (M1 i c sup) where
111+ gupcast = M1 . gupcast
112+
113+--------------------------------------------------------------------------------
114+-- * Generic smashing
115+
116+class GSmash sub sup where
117+ gsmash :: sup p -> sub p -> sub p
118+
119+instance (GSmash a sup, GSmash b sup) => GSmash (a :*: b) sup where
120+ gsmash rep (a :*: b) = gsmash rep a :*: gsmash rep b
121+
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)
74127
75−-- | Convert 'rep' into 'f'
76−class Convert (rep :: Type -> Type) (f :: Type -> Type) where
77− convert :: rep p -> f p
128+instance GSmash sub sup => GSmash (M1 i c sub) sup where
129+ gsmash sup (M1 sub) = M1 (gsmash sup sub)
78130
79−instance (Convert rep a, Convert rep b) => Convert rep (a :*: b) where
80− convert rep = convert rep :*: convert rep
131+class GSmashLeaf sub sup (w :: Maybe Type) where
132+ gsmashLeaf :: sup p -> sub p -> sub p
81133
82−instance {-# OVERLAPPING #-} GHasField field rep t => Convert rep (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) where
83− convert rep = M1 (K1 (rep ^. glabel @field))
134+instance (GHasField field sup t) => GSmashLeaf (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) sup ('Just t) where
135+ gsmashLeaf sup (M1 (K1 _)) = M1 (K1 (sup ^. glabel @field))
84136
85−instance Convert rep f => Convert rep (M1 i c f) where
86− convert = M1 . convert
137+instance GSmashLeaf (S1 ('MetaSel ('Just field) p f b) (Rec0 t)) sup 'Nothing where
138+ gsmashLeaf _ = id