row-types 0.4.0.0 → 1.0.0.0
raw patch · 17 files changed
+2665/−2043 lines, 17 filesdep +gaugedep −criteriondep ~constraintsPVP ok
version bump matches the API change (PVP)
Dependencies added: gauge
Dependencies removed: criterion
Dependency ranges changed: constraints
API changes (from Hackage documentation)
- Data.Row: class Switch (v :: Row *) (r :: Row *) x | v x -> r, r x -> v
- Data.Row.Internal: [As] :: forall c f a t. (a ~ f t, c t) => As c f a
- Data.Row.Internal: as :: IsA c f a => As c f a
- Data.Row.Internal: biMetamorph' :: forall (f :: Row k1 -> Row k2 -> *) (g :: Row k1 -> Row k2 -> *) (h :: k1 -> k2 -> *). BiForall r1 r2 c => Proxy h -> (f Empty Empty -> g Empty Empty) -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) => Label ℓ -> f ( 'R ((ℓ :-> τ1) : ρ1)) ( 'R ((ℓ :-> τ2) : ρ2)) -> Either (h τ1 τ2) (f ( 'R ρ1) ( 'R ρ2))) -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) => Label ℓ -> Either (h τ1 τ2) (g ( 'R ρ1) ( 'R ρ2)) -> g ( 'R ((ℓ :-> τ1) : ρ1)) ( 'R ((ℓ :-> τ2) : ρ2))) -> f r1 r2 -> g r1 r2
- Data.Row.Internal: class IsA c f a
- Data.Row.Internal: data As c f a
- Data.Row.Internal: freeForall :: forall r c. Forall r c :- Forall r Unconstrained1
- Data.Row.Internal: instance forall k (ℓ :: GHC.Types.Symbol) (c :: k -> GHC.Types.Constraint) (τ :: k) (ρ :: [Data.Row.Internal.LT k]). (GHC.TypeLits.KnownSymbol ℓ, c τ, Data.Row.Internal.Forall ('Data.Row.Internal.R ρ) c) => Data.Row.Internal.Forall ('Data.Row.Internal.R ((ℓ 'Data.Row.Internal.:-> τ) : ρ)) c
- Data.Row.Internal: instance forall k1 k2 (c :: k2 -> GHC.Types.Constraint) (a :: k2) (f :: k2 -> k1). c a => Data.Row.Internal.IsA c f (f a)
- Data.Row.Internal: instance forall k1 k2 (ℓ :: GHC.Types.Symbol) (c :: k1 -> k2 -> GHC.Types.Constraint) (τ1 :: k1) (τ2 :: k2) (ρ1 :: [Data.Row.Internal.LT k1]) (ρ2 :: [Data.Row.Internal.LT k2]). (GHC.TypeLits.KnownSymbol ℓ, c τ1 τ2, Data.Row.Internal.BiForall ('Data.Row.Internal.R ρ1) ('Data.Row.Internal.R ρ2) c) => Data.Row.Internal.BiForall ('Data.Row.Internal.R ((ℓ 'Data.Row.Internal.:-> τ1) : ρ1)) ('Data.Row.Internal.R ((ℓ 'Data.Row.Internal.:-> τ2) : ρ2)) c
- Data.Row.Internal: mapForall :: forall f c ρ. Forall ρ c :- Forall (Map f ρ) (IsA c f)
- Data.Row.Internal: mapHas :: forall f r l t. ((r .! l) ≈ t) :- ((Map f r .! l) ≈ f t)
- Data.Row.Internal: metamorph' :: forall (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). Forall r c => Proxy h -> (f Empty -> g Empty) -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> f ( 'R ((ℓ :-> τ) : ρ)) -> Either (h τ) (f ( 'R ρ))) -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> Either (h τ) (g ( 'R ρ)) -> g ( 'R ((ℓ :-> τ) : ρ))) -> f r -> g r
- Data.Row.Internal: uniqueMap :: forall f ρ. AllUniqueLabels ρ :- AllUniqueLabels (Map f ρ)
- Data.Row.Records: instance (Data.Row.Records.GenericRec ('Data.Row.Internal.R ((name' 'Data.Row.Internal.:-> t') : r')), GHC.TypeLits.KnownSymbol name) => Data.Row.Records.GenericRec ('Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'))
- Data.Row.Records: instance forall k (l :: k -> *) (r :: k -> *). (Data.Row.Records.FromNativeG l, Data.Row.Records.FromNativeG r) => Data.Row.Records.FromNativeG (l GHC.Generics.:*: r)
- Data.Row.Records: unsafeInjectFront :: KnownSymbol l => Label l -> a -> Rec (R r) -> Rec (R ((l :-> a) : r))
- Data.Row.Records: unsafeRemove :: KnownSymbol l => Label l -> Rec r -> Rec (r .- l)
- Data.Row.Switch: class Switch (v :: Row *) (r :: Row *) x | v x -> r, r x -> v
- Data.Row.Switch: instance (GHC.TypeLits.KnownSymbol l, Data.Row.Switch.Switch ('Data.Row.Internal.R v) ('Data.Row.Internal.R r) b) => Data.Row.Switch.Switch ('Data.Row.Internal.R ((l 'Data.Row.Internal.:-> a) : v)) ('Data.Row.Internal.R ((l 'Data.Row.Internal.:-> (a -> b)) : r)) b
- Data.Row.Switch: instance Data.Row.Switch.Switch ('Data.Row.Internal.R '[]) ('Data.Row.Internal.R '[]) x
- Data.Row.Variants: instance (Data.Row.Variants.GenericVar ('Data.Row.Internal.R ((name' 'Data.Row.Internal.:-> t') : r')), GHC.TypeLits.KnownSymbol name, Data.Row.Internal.AllUniqueLabels ('Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'))) => Data.Row.Variants.GenericVar ('Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'))
- Data.Row.Variants: instance forall k (l :: k -> *) (r :: k -> *). (Data.Row.Variants.ToNativeG l, Data.Row.Variants.ToNativeG r, ((Data.Row.Variants.NativeRowG l Data.Row.Internal..+ Data.Row.Variants.NativeRowG r) Data.Row.Internal..\\ Data.Row.Variants.NativeRowG l) Data.Row.Internal.≈ Data.Row.Variants.NativeRowG r, Data.Row.Internal.AllUniqueLabels (Data.Row.Variants.NativeRowG l), Data.Row.Internal.Forall (Data.Row.Variants.NativeRowG r) Data.Row.Internal.Unconstrained1) => Data.Row.Variants.ToNativeG (l GHC.Generics.:+: r)
- Data.Row.Variants: unsafeInjectFront :: forall l a r. KnownSymbol l => Var (R r) -> Var (R ((l :-> a) : r))
- Data.Row.Variants: unsafeMakeVar :: forall r l. KnownSymbol l => Label l -> (r .! l) -> Var r
+ Data.Row: type FreeBiForall r1 r2 = BiForall r1 r2 Unconstrained2
+ Data.Row: type FreeForall r = Forall r Unconstrained1
+ Data.Row.Dictionaries: (\\) :: HasDict c e => (c -> r) -> e -> r
+ Data.Row.Dictionaries: Sub :: (a -> Dict b) -> (:-) a b
+ Data.Row.Dictionaries: [As'] :: forall c f a t. (a ~ f t, c f) => As' c t a
+ Data.Row.Dictionaries: [As] :: forall c f a t. (a ~ f t, c t) => As c f a
+ Data.Row.Dictionaries: [Dict] :: forall a. a => Dict a
+ Data.Row.Dictionaries: actsOn :: ActsOn c t a => As' c t a
+ Data.Row.Dictionaries: apExtendSwap :: forall ℓ f fs τ r. Dict (Extend ℓ (f τ) (Ap fs r) ≈ Ap (Extend ℓ f fs) (Extend ℓ τ r))
+ Data.Row.Dictionaries: apHas :: forall l f ϕ t ρ. ((ϕ .! l) ≈ f, (ρ .! l) ≈ t) :- ((Ap ϕ ρ .! l) ≈ f t, (Ap ϕ ρ .- l) ≈ Ap (ϕ .- l) (ρ .- l))
+ Data.Row.Dictionaries: apSingleExtendSwap :: forall τ ℓ f r. Dict (Extend ℓ (f τ) (ApSingle r τ) ≈ ApSingle (Extend ℓ f r) τ)
+ Data.Row.Dictionaries: apSingleForall :: forall a fs c. Forall fs c :- Forall (ApSingle fs a) (ActsOn c a)
+ Data.Row.Dictionaries: apSingleHas :: forall x l f r. ((r .! l) ≈ f) :- ((ApSingle r x .! l) ≈ f x, (ApSingle r x .- l) ≈ ApSingle (r .- l) x)
+ Data.Row.Dictionaries: as :: IsA c f a => As c f a
+ Data.Row.Dictionaries: class ActsOn c t a
+ Data.Row.Dictionaries: class HasDict c e | e -> c
+ Data.Row.Dictionaries: class IsA c f a
+ Data.Row.Dictionaries: class Unconstrained
+ Data.Row.Dictionaries: class Unconstrained1 a
+ Data.Row.Dictionaries: class Unconstrained2 a b
+ Data.Row.Dictionaries: data As c f a
+ Data.Row.Dictionaries: data As' c t a
+ Data.Row.Dictionaries: data Dict a
+ Data.Row.Dictionaries: evidence :: HasDict c e => e -> Dict c
+ Data.Row.Dictionaries: extendHas :: forall l t r. Dict ((Extend l t r .! l) ≈ t)
+ Data.Row.Dictionaries: freeForall :: forall r c. Forall r c :- Forall r Unconstrained1
+ Data.Row.Dictionaries: infixl 1 \\
+ Data.Row.Dictionaries: infixr 9 :-
+ Data.Row.Dictionaries: instance forall k1 k2 (c :: (k1 -> k2) -> GHC.Types.Constraint) (f :: k1 -> k2) (t :: k1). c f => Data.Row.Dictionaries.ActsOn c t (f t)
+ Data.Row.Dictionaries: instance forall k1 k2 (c :: k2 -> GHC.Types.Constraint) (a :: k2) (f :: k2 -> k1). c a => Data.Row.Dictionaries.IsA c f (f a)
+ Data.Row.Dictionaries: mapExtendSwap :: forall f ℓ τ r. Dict (Extend ℓ (f τ) (Map f r) ≈ Map f (Extend ℓ τ r))
+ Data.Row.Dictionaries: mapForall :: forall f ρ c. Forall ρ c :- Forall (Map f ρ) (IsA c f)
+ Data.Row.Dictionaries: mapHas :: forall f l t r. ((r .! l) ≈ t) :- ((Map f r .! l) ≈ f t, (Map f r .- l) ≈ Map f (r .- l))
+ Data.Row.Dictionaries: newtype (:-) a b
+ Data.Row.Dictionaries: type FreeBiForall r1 r2 = BiForall r1 r2 Unconstrained2
+ Data.Row.Dictionaries: type FreeForall r = Forall r Unconstrained1
+ Data.Row.Dictionaries: uniqueAp :: forall fs r. Dict (AllUniqueLabels (Ap fs r) ≈ AllUniqueLabels r)
+ Data.Row.Dictionaries: uniqueApSingle :: forall x r. Dict (AllUniqueLabels (ApSingle r x) ≈ AllUniqueLabels r)
+ Data.Row.Dictionaries: uniqueMap :: forall f r. Dict (AllUniqueLabels (Map f r) ≈ AllUniqueLabels r)
+ Data.Row.Dictionaries: uniqueZip :: forall r1 r2. Dict (AllUniqueLabels (Zip r1 r2) ≈ (AllUniqueLabels r1, AllUniqueLabels r2))
+ Data.Row.Dictionaries: withDict :: HasDict c e => e -> (c -> r) -> r
+ Data.Row.Dictionaries: zipExtendSwap :: forall ℓ τ1 r1 τ2 r2. Dict (Extend ℓ (τ1, τ2) (Zip r1 r2) ≈ Zip (Extend ℓ τ1 r1) (Extend ℓ τ2 r2))
+ Data.Row.Internal: FrontExtendsDict :: Dict (r ~ R ρ, R ((l :-> t) : ρ) ≈ Extend l t (R ρ), AllUniqueLabelsR ((l :-> t) : ρ)) -> FrontExtendsDict l t r
+ Data.Row.Internal: class FrontExtends l t r
+ Data.Row.Internal: class Unconstrained
+ Data.Row.Internal: data FrontExtendsDict l t r
+ Data.Row.Internal: frontExtendsDict :: FrontExtends l t r => FrontExtendsDict l t r
+ Data.Row.Internal: instance forall k (r :: Data.Row.Internal.Row k) (ρ :: [Data.Row.Internal.LT k]) (l :: GHC.Types.Symbol) (t :: k). (r Data.Type.Equality.~ 'Data.Row.Internal.R ρ, 'Data.Row.Internal.R ((l 'Data.Row.Internal.:-> t) : ρ) Data.Row.Internal.≈ Data.Row.Internal.Extend l t ('Data.Row.Internal.R ρ), Data.Row.Internal.AllUniqueLabelsR ((l 'Data.Row.Internal.:-> t) : ρ)) => Data.Row.Internal.FrontExtends l t r
+ Data.Row.Internal: instance forall k (ℓ :: GHC.Types.Symbol) (c :: k -> GHC.Types.Constraint) (τ :: k) (ρ :: [Data.Row.Internal.LT k]). (GHC.TypeLits.KnownSymbol ℓ, c τ, Data.Row.Internal.Forall ('Data.Row.Internal.R ρ) c, Data.Row.Internal.FrontExtends ℓ τ ('Data.Row.Internal.R ρ), Data.Row.Internal.AllUniqueLabels (Data.Row.Internal.Extend ℓ τ ('Data.Row.Internal.R ρ))) => Data.Row.Internal.Forall ('Data.Row.Internal.R ((ℓ 'Data.Row.Internal.:-> τ) : ρ)) c
+ Data.Row.Internal: instance forall k1 k2 (ℓ :: GHC.Types.Symbol) (c :: k1 -> k2 -> GHC.Types.Constraint) (τ1 :: k1) (τ2 :: k2) (ρ1 :: [Data.Row.Internal.LT k1]) (ρ2 :: [Data.Row.Internal.LT k2]). (GHC.TypeLits.KnownSymbol ℓ, c τ1 τ2, Data.Row.Internal.BiForall ('Data.Row.Internal.R ρ1) ('Data.Row.Internal.R ρ2) c, Data.Row.Internal.FrontExtends ℓ τ1 ('Data.Row.Internal.R ρ1), Data.Row.Internal.FrontExtends ℓ τ2 ('Data.Row.Internal.R ρ2), Data.Row.Internal.AllUniqueLabels (Data.Row.Internal.Extend ℓ τ1 ('Data.Row.Internal.R ρ1)), Data.Row.Internal.AllUniqueLabels (Data.Row.Internal.Extend ℓ τ2 ('Data.Row.Internal.R ρ2))) => Data.Row.Internal.BiForall ('Data.Row.Internal.R ((ℓ 'Data.Row.Internal.:-> τ1) : ρ1)) ('Data.Row.Internal.R ((ℓ 'Data.Row.Internal.:-> τ2) : ρ2)) c
+ Data.Row.Records: (.$) :: (KnownSymbol l, (r' .! l) ≈ t) => (Rec ((l .== t) .+ r) -> x) -> (Label l, Rec r') -> Rec r -> x
+ Data.Row.Records: curryRec :: forall l t r x. KnownSymbol l => Label l -> (Rec ((l .== t) .+ r) -> x) -> t -> Rec r -> x
+ Data.Row.Records: distribute :: forall f r. (FreeForall r, Functor f) => f (Rec r) -> Rec (Map f r)
+ Data.Row.Records: infixl 2 .$
+ Data.Row.Records: instance (r Data.Type.Equality.~ ((name' 'Data.Row.Internal.:-> t') : r'), Data.Row.Records.GenericRec ('Data.Row.Internal.R r), GHC.TypeLits.KnownSymbol name, Data.Row.Internal.Extend name t ('Data.Row.Internal.R r) Data.Row.Internal.≈ 'Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : r)) => Data.Row.Records.GenericRec ('Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'))
+ Data.Row.Records: instance forall k (l :: k -> *) (r :: k -> *). (Data.Row.Records.FromNativeG l, Data.Row.Records.FromNativeG r, Data.Row.Dictionaries.FreeForall (Data.Row.Records.NativeRowG l)) => Data.Row.Records.FromNativeG (l GHC.Generics.:*: r)
+ Data.Row.Records: lazyRemove :: KnownSymbol l => Label l -> Rec r -> Rec (r .- l)
+ Data.Row.Records: traverse :: forall c f r. (Forall r c, Applicative f) => (forall a. c a => a -> f a) -> Rec r -> f (Rec r)
+ Data.Row.Records: traverseMap :: forall c (f :: * -> *) (g :: * -> *) (h :: * -> *) r. (Forall r c, Applicative f) => (forall a. c a => g a -> f (h a)) -> Rec (Map g r) -> f (Rec (Map h r))
+ Data.Row.Records: zipTransform :: forall c r (f :: * -> *) (g :: * -> *) (h :: * -> *). Forall r c => (forall a. c a => f a -> g a -> h a) -> Rec (Map f r) -> Rec (Map g r) -> Rec (Map h r)
+ Data.Row.Records: zipTransform' :: forall r (f :: * -> *) (g :: * -> *) (h :: * -> *). FreeForall r => (forall a. f a -> g a -> h a) -> Rec (Map f r) -> Rec (Map g r) -> Rec (Map h r)
+ Data.Row.Switch: applyTo :: AppliesTo r f x => f -> x -> r
+ Data.Row.Switch: class AppliesTo r f x | r x -> f, f r -> x
+ Data.Row.Switch: instance Data.Row.Switch.AppliesTo r (x -> r) x
+ Data.Row.Variants: eraseSingle :: forall (c :: (* -> *) -> Constraint) (fs :: Row (* -> *)) (x :: *) (y :: *). Forall fs c => (forall f. c f => f x -> y) -> Var (ApSingle fs x) -> y
+ Data.Row.Variants: eraseZipGeneral :: forall c ρ b s. (Forall ρ c, IsString s) => (forall x y. (c x, c y) => Either (s, x, x) ((s, x), (s, y)) -> b) -> Var ρ -> Var ρ -> b
+ Data.Row.Variants: eraseZipSingle :: forall c fs (x :: *) (y :: *) z. Forall fs c => (forall f. c f => f x -> f y -> z) -> Var (ApSingle fs x) -> Var (ApSingle fs y) -> Maybe z
+ Data.Row.Variants: extend :: forall a l r. KnownSymbol l => Label l -> Var r -> Var (Extend l a r)
+ Data.Row.Variants: fromLabelsMap :: forall c f g ρ. (Alternative f, Forall ρ c, AllUniqueLabels ρ) => (forall l a. (KnownSymbol l, c a) => Label l -> f (g a)) -> f (Var (Map g ρ))
+ Data.Row.Variants: instance (Data.Row.Variants.GenericVar ('Data.Row.Internal.R ((name' 'Data.Row.Internal.:-> t') : r')), GHC.TypeLits.KnownSymbol name, Data.Row.Internal.Extend name t ('Data.Row.Internal.R ((name' 'Data.Row.Internal.:-> t') : r')) Data.Row.Internal.≈ 'Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'), Data.Row.Internal.AllUniqueLabels ('Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'))) => Data.Row.Variants.GenericVar ('Data.Row.Internal.R ((name 'Data.Row.Internal.:-> t) : (name' 'Data.Row.Internal.:-> t') : r'))
+ Data.Row.Variants: instance forall k (l :: k -> *) (r :: k -> *). (Data.Row.Variants.ToNativeG l, Data.Row.Variants.ToNativeG r, ((Data.Row.Variants.NativeRowG l Data.Row.Internal..+ Data.Row.Variants.NativeRowG r) Data.Row.Internal..\\ Data.Row.Variants.NativeRowG r) Data.Row.Internal.≈ Data.Row.Variants.NativeRowG l, Data.Row.Internal.AllUniqueLabels (Data.Row.Variants.NativeRowG r), Data.Row.Internal.Forall (Data.Row.Variants.NativeRowG l) Data.Row.Internal.Unconstrained1) => Data.Row.Variants.ToNativeG (l GHC.Generics.:+: r)
+ Data.Row.Variants: mapSingle :: forall (c :: (* -> *) -> Constraint) (fs :: Row (* -> *)) (x :: *) (y :: *). Forall fs c => (forall f. c f => f x -> f y) -> Var (ApSingle fs x) -> Var (ApSingle fs y)
+ Data.Row.Variants: traverse :: forall c f r. (Forall r c, Applicative f) => (forall a. c a => a -> f a) -> Var r -> f (Var r)
+ Data.Row.Variants: traverseMap :: forall c (f :: * -> *) (g :: * -> *) (h :: * -> *) r. (Forall r c, Applicative f) => (forall a. c a => g a -> f (h a)) -> Var (Map g r) -> f (Var (Map h r))
- Data.Row: (.+) :: Rec l -> Rec r -> Rec (l .+ r)
+ Data.Row: (.+) :: forall l r. FreeForall l => Rec l -> Rec r -> Rec (l .+ r)
- Data.Row: caseon :: Switch v r x => Rec r -> Var v -> x
+ Data.Row: caseon :: forall v r x. BiForall r v (AppliesTo x) => Rec r -> Var v -> x
- Data.Row: multiTrial :: forall x y. (AllUniqueLabels x, Forall (y .\\ x) Unconstrained1) => Var y -> Either (Var x) (Var (y .\\ x))
+ Data.Row: multiTrial :: forall x y. (AllUniqueLabels x, Forall (y .\\ x) Unconstrained1) => Var y -> Either (Var (y .\\ x)) (Var x)
- Data.Row: switch :: Switch v r x => Var v -> Rec r -> x
+ Data.Row: switch :: forall v r x. BiForall r v (AppliesTo x) => Var v -> Rec r -> x
- Data.Row: trial :: KnownSymbol l => Var r -> Label l -> Either (r .! l) (Var (r .- l))
+ Data.Row: trial :: KnownSymbol l => Var r -> Label l -> Either (Var (r .- l)) (r .! l)
- Data.Row.Internal: biMetamorph :: forall (f :: Row k1 -> Row k2 -> *) (g :: Row k1 -> Row k2 -> *) (h :: k1 -> k2 -> *). BiForall r1 r2 c => Proxy h -> (f Empty Empty -> g Empty Empty) -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) => Label ℓ -> f ( 'R ((ℓ :-> τ1) : ρ1)) ( 'R ((ℓ :-> τ2) : ρ2)) -> (h τ1 τ2, f ( 'R ρ1) ( 'R ρ2))) -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) => Label ℓ -> h τ1 τ2 -> g ( 'R ρ1) ( 'R ρ2) -> g ( 'R ((ℓ :-> τ1) : ρ1)) ( 'R ((ℓ :-> τ2) : ρ2))) -> f r1 r2 -> g r1 r2
+ Data.Row.Internal: biMetamorph :: forall (p :: * -> * -> *) (f :: Row k1 -> Row k2 -> *) (g :: Row k1 -> Row k2 -> *) (h :: k1 -> k2 -> *). (BiForall r1 r2 c, Bifunctor p) => Proxy (Proxy h, Proxy p) -> (f Empty Empty -> g Empty Empty) -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2, HasType ℓ τ1 ρ1, HasType ℓ τ2 ρ2) => Label ℓ -> f ρ1 ρ2 -> p (f (ρ1 .- ℓ) (ρ2 .- ℓ)) (h τ1 τ2)) -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2, FrontExtends ℓ τ1 ρ1, FrontExtends ℓ τ2 ρ2, AllUniqueLabels (Extend ℓ τ1 ρ1), AllUniqueLabels (Extend ℓ τ2 ρ2)) => Label ℓ -> p (g ρ1 ρ2) (h τ1 τ2) -> g (Extend ℓ τ1 ρ1) (Extend ℓ τ2 ρ2)) -> f r1 r2 -> g r1 r2
- Data.Row.Internal: metamorph :: forall (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). Forall r c => Proxy h -> (f Empty -> g Empty) -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> f ( 'R ((ℓ :-> τ) : ρ)) -> (h τ, f ( 'R ρ))) -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> h τ -> g ( 'R ρ) -> g ( 'R ((ℓ :-> τ) : ρ))) -> f r -> g r
+ Data.Row.Internal: metamorph :: forall (p :: * -> * -> *) (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). (Forall r c, Bifunctor p) => Proxy (Proxy h, Proxy p) -> (f Empty -> g Empty) -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) => Label ℓ -> f ρ -> p (f (ρ .- ℓ)) (h τ)) -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ, FrontExtends ℓ τ ρ, AllUniqueLabels (Extend ℓ τ ρ)) => Label ℓ -> p (g ρ) (h τ) -> g (Extend ℓ τ ρ)) -> f r -> g r
- Data.Row.Records: (.+) :: Rec l -> Rec r -> Rec (l .+ r)
+ Data.Row.Records: (.+) :: forall l r. FreeForall l => Rec l -> Rec r -> Rec (l .+ r)
- Data.Row.Records: compose :: forall (f :: * -> *) (g :: * -> *) r. Forall r Unconstrained1 => Rec (Map f (Map g r)) -> Rec (Map (Compose f g) r)
+ Data.Row.Records: compose :: forall (f :: * -> *) (g :: * -> *) r. FreeForall r => Rec (Map f (Map g r)) -> Rec (Map (Compose f g) r)
- Data.Row.Records: map' :: forall f r. Forall r Unconstrained1 => (forall a. a -> f a) -> Rec r -> Rec (Map f r)
+ Data.Row.Records: map' :: forall f r. FreeForall r => (forall a. a -> f a) -> Rec r -> Rec (Map f r)
- Data.Row.Records: mapF :: forall k c g (ϕ :: Row (k -> *)) (ρ :: Row k). BiForall ϕ ρ c => (forall f a. c f a => f a -> f (g a)) -> Rec (Ap ϕ ρ) -> Rec (Ap ϕ (Map g ρ))
+ Data.Row.Records: mapF :: forall k c g (ϕ :: Row (k -> *)) (ρ :: Row k). BiForall ϕ ρ c => (forall h a. c h a => h a -> h (g a)) -> Rec (Ap ϕ ρ) -> Rec (Ap ϕ (Map g ρ))
- Data.Row.Records: restrict :: forall r r'. (Forall r Unconstrained1, Subset r r') => Rec r' -> Rec r
+ Data.Row.Records: restrict :: forall r r'. (FreeForall r, Subset r r') => Rec r' -> Rec r
- Data.Row.Records: sequence :: forall f r. (Forall r Unconstrained1, Applicative f) => Rec (Map f r) -> f (Rec r)
+ Data.Row.Records: sequence :: forall f r. (Applicative f, FreeForall r) => Rec (Map f r) -> f (Rec r)
- Data.Row.Records: split :: forall s r. (Forall s Unconstrained1, Subset s r) => Rec r -> (Rec s, Rec (r .\\ s))
+ Data.Row.Records: split :: forall s r. (Subset s r, FreeForall s) => Rec r -> (Rec s, Rec (r .\\ s))
- Data.Row.Records: transform' :: forall r (f :: * -> *) (g :: * -> *). Forall r Unconstrained1 => (forall a. f a -> g a) -> Rec (Map f r) -> Rec (Map g r)
+ Data.Row.Records: transform' :: forall r (f :: * -> *) (g :: * -> *). FreeForall r => (forall a. f a -> g a) -> Rec (Map f r) -> Rec (Map g r)
- Data.Row.Records: uncompose :: forall (f :: * -> *) (g :: * -> *) r. Forall r Unconstrained1 => Rec (Map (Compose f g) r) -> Rec (Map f (Map g r))
+ Data.Row.Records: uncompose :: forall (f :: * -> *) (g :: * -> *) r. FreeForall r => Rec (Map (Compose f g) r) -> Rec (Map f (Map g r))
- Data.Row.Records: zip :: forall r1 r2. BiForall r1 r2 Unconstrained2 => Rec r1 -> Rec r2 -> Rec (Zip r1 r2)
+ Data.Row.Records: zip :: forall r1 r2. FreeBiForall r1 r2 => Rec r1 -> Rec r2 -> Rec (Zip r1 r2)
- Data.Row.Switch: caseon :: Switch v r x => Rec r -> Var v -> x
+ Data.Row.Switch: caseon :: forall v r x. BiForall r v (AppliesTo x) => Rec r -> Var v -> x
- Data.Row.Switch: switch :: Switch v r x => Var v -> Rec r -> x
+ Data.Row.Switch: switch :: forall v r x. BiForall r v (AppliesTo x) => Var v -> Rec r -> x
- Data.Row.Variants: compose :: forall (f :: * -> *) (g :: * -> *) r. Forall r Unconstrained1 => Var (Map f (Map g r)) -> Var (Map (Compose f g) r)
+ Data.Row.Variants: compose :: forall (f :: * -> *) (g :: * -> *) r. FreeForall r => Var (Map f (Map g r)) -> Var (Map (Compose f g) r)
- Data.Row.Variants: map' :: forall f r. Forall r Unconstrained1 => (forall a. a -> f a) -> Var r -> Var (Map f r)
+ Data.Row.Variants: map' :: forall f r. FreeForall r => (forall a. a -> f a) -> Var r -> Var (Map f r)
- Data.Row.Variants: multiTrial :: forall x y. (AllUniqueLabels x, Forall (y .\\ x) Unconstrained1) => Var y -> Either (Var x) (Var (y .\\ x))
+ Data.Row.Variants: multiTrial :: forall x y. (AllUniqueLabels x, Forall (y .\\ x) Unconstrained1) => Var y -> Either (Var (y .\\ x)) (Var x)
- Data.Row.Variants: sequence :: forall f r. (Forall r Unconstrained1, Applicative f) => Var (Map f r) -> f (Var r)
+ Data.Row.Variants: sequence :: forall f r. (FreeForall r, Applicative f) => Var (Map f r) -> f (Var r)
- Data.Row.Variants: split :: forall s r. (WellBehaved s, Subset s r) => Var r -> Either (Var s) (Var (r .\\ s))
+ Data.Row.Variants: split :: forall s r. (WellBehaved s, Subset s r) => Var r -> Either (Var (r .\\ s)) (Var s)
- Data.Row.Variants: transform :: forall r c (f :: * -> *) (g :: * -> *). Forall r c => (forall a. c a => f a -> g a) -> Var (Map f r) -> Var (Map g r)
+ Data.Row.Variants: transform :: forall c r (f :: * -> *) (g :: * -> *). Forall r c => (forall a. c a => f a -> g a) -> Var (Map f r) -> Var (Map g r)
- Data.Row.Variants: transform' :: forall r (f :: * -> *) (g :: * -> *). Forall r Unconstrained1 => (forall a. f a -> g a) -> Var (Map f r) -> Var (Map g r)
+ Data.Row.Variants: transform' :: forall r (f :: * -> *) (g :: * -> *). FreeForall r => (forall a. f a -> g a) -> Var (Map f r) -> Var (Map g r)
- Data.Row.Variants: trial :: KnownSymbol l => Var r -> Label l -> Either (r .! l) (Var (r .- l))
+ Data.Row.Variants: trial :: KnownSymbol l => Var r -> Label l -> Either (Var (r .- l)) (r .! l)
- Data.Row.Variants: uncompose :: forall (f :: * -> *) (g :: * -> *) r. Forall r Unconstrained1 => Var (Map (Compose f g) r) -> Var (Map f (Map g r))
+ Data.Row.Variants: uncompose :: forall (f :: * -> *) (g :: * -> *) r. FreeForall r => Var (Map (Compose f g) r) -> Var (Map f (Map g r))
Files
- CHANGELOG.md +17/−1
- Data/Row.hs +0/−56
- Data/Row/Internal.hs +0/−573
- Data/Row/Records.hs +0/−706
- Data/Row/Switch.hs +0/−45
- Data/Row/Variants.hs +0/−565
- benchmarks/perf/Main.hs +44/−44
- examples/Examples.lhs +18/−13
- row-types.cabal +30/−40
- src/Data/Row.hs +60/−0
- src/Data/Row/Dictionaries.hs +186/−0
- src/Data/Row/Internal.hs +518/−0
- src/Data/Row/Records.hs +837/−0
- src/Data/Row/Switch.hs +65/−0
- src/Data/Row/Variants.hs +761/−0
- src/aeson/Data/Row/Aeson.hs +52/−0
- src/barbies/Data/Row/Barbies.hs +77/−0
CHANGELOG.md view
@@ -1,3 +1,19 @@+## 1.0.0.0 [2020-09-12] +This release has many breaking changes, specifically to `metamorph` and some functions related to `Variant`, hence the major version bump to `1.0`. However, users that only use basic features of records may not notice a difference. + +- Removed `metamorph'` and `biMetamorph'` in favor of generalizing `metamorph` over choice of bifunctor. +- Removed "unsafe" functions (`unsafeRemove` and `unsafeInjectFront` from `Records` and `unsafeMarkVar` and `unsafeInjectFront` from `Variants`). +- Removed `Switch` class, reimplementing the `switch` function using `BiForall`. +- Swap the order of the result of calling `trial`, `multiTrial`, and `split`. +- Added new functions to `Records`: `lazyRemove`, `curryRec`, `(.$)`, `zipTransform`, `zipTransform'`, `traverse`, `traverseMap`, `distribute`, and `coerceRec`. +- Added new functions to `Variants`: `fromLabelsMap`, `traverse`, `traverseMap`, and `coerceVar`. +- Added `Dictionaries` module, full of axioms that are helpful for using `metamorph`. Moved axioms from `Internal` to `Dictionaries` (in some cases, the type variable order has changed). +- Added `ApSingle` type family as well as `eraseSingle`, `mapSingle`, and `eraseZipSingle` (thanks Jordan Woehr!). +- Improved error messages. + +Note: GHC 8.4 and earlier are no longer officially supported in row-types 1.0.0.0. + + ## 0.4.0.0 [2020-05-20] - Renamed `toNative` to `toNativeGeneral` and `toNativeExact` to `toNative` for records and likewise for `fromNative` for variants. - Added a type family `NativeRow` which, when given any generic type that can go through `fromNative`, is equal to the row-type of the resulting record/variant. Note that `NativeRow` is defined separately (and differently!) for records vs variants, so it is exported at the `Data.Row.Records`/`Variants` level but not at `Data.Row`. @@ -5,7 +21,7 @@ - Exposed `BiForall` in `Data.Row`, `Data.Row.Records`, and `Data.Row.Variants` - (Internal) Rewrote internal `Generic` code to use an associated type family instead of a standalone one. -Note: GHC 8.2 and earlier are no longer supported in row-types 0.4.0.0. +Note: GHC 8.2 and earlier are no longer officially supported in row-types 0.4.0.0. ## 0.3.1.0 [2020-01-29] - Added "native" classes as exports for `Records` and `Variants` (e.g., `ToNative`, `FromNative`)
− Data/Row.hs
@@ -1,56 +0,0 @@------------------------------------------------------------------------------ --- | --- Module : Data.Row --- --- This module includes a set of common functions for Records and Variants. --- It includes: --- --- * Common constructors, destructors, and querying functions --- --- It specifically excludes: --- --- * Functions that have the same name for Records and Variants (e.g. 'focus', --- 'update', 'fromLabels', etc.) --- --- * Common clashes with the standard Prelude or other modules (e.g. 'map', --- 'sequence', 'zip', 'Map', etc.) --- --- If these particular functions are needed, they should be brought in qualified --- from one of the Data.Row.*** modules directly. --- ------------------------------------------------------------------------------ - - -module Data.Row - ( - -- * Types and constraints - Label(..) - , KnownSymbol, AllUniqueLabels, WellBehaved - , Var, Rec, Row, Empty, type (≈) - , HasType, Lacks, type (.\), type (.+) - , type (.\/), type (.\\), type (.//) - , BiForall, Forall, Switch(..) - -- * Record Construction - , empty - , type (.==), (.==), pattern (:==) - -- ** Restriction - , type (.-), (.-) - -- ** Query - , type (.!), (.!) - -- ** Union - , (.+), Disjoint, pattern (:+) - , (.//) - -- * Variant construction - , pattern IsJust - -- ** Expansion - , diversify - -- ** Destruction - , impossible, trial, trial', multiTrial - -- * Labels - , labels - ) -where - -import Data.Row.Variants -import Data.Row.Records -import Data.Row.Switch
− Data/Row/Internal.hs
@@ -1,573 +0,0 @@-{-# LANGUAGE CPP #-} -{-# LANGUAGE UndecidableSuperClasses #-} ------------------------------------------------------------------------------ --- | --- Module : Data.Row.Internal --- --- This module implements the internals of open records and variants. --- ------------------------------------------------------------------------------ - - -module Data.Row.Internal - ( - -- * Rows - Row(..) - , Label(..) - , KnownSymbol - , LT(..) - , Empty - , HideType(..) - -- * Row Operations - , Extend, Modify, Rename - , type (.==), type (.!), type (.-), type (.\\) - -- $merges - , type (.+), type (.\/), type (.//) - -- * Row Constraints - , Lacks, type (.\), HasType - , Forall(..) - , BiForall(..) - , BiConstraint - , Unconstrained1 - , Unconstrained2 - , WellBehaved, AllUniqueLabels - , Ap, Zip, Map, Subset, Disjoint - -- * Helper functions - , Labels, labels, labels' - , show' - , toKey - , type (≈) - - , mapForall - , freeForall - , uniqueMap - , mapHas - , IsA(..) - , As(..) - ) -where - -import Data.Constraint -import Data.Functor.Const -import Data.Proxy -import Data.String (IsString (fromString)) -import Data.Text (Text) -import qualified Data.Text as Text -import Data.Type.Equality (type (==)) - -import qualified Unsafe.Coerce as UNSAFE - -import GHC.OverloadedLabels -import GHC.TypeLits -import qualified GHC.TypeLits as TL - - - - -{-------------------------------------------------------------------- - Rows ---------------------------------------------------------------------} --- | The kind of rows. This type is only used as a datakind. A row is a typelevel entity telling us --- which symbols are associated with which types. -newtype Row a = R [LT a] - -- ^ A row is a list of symbol-to-type pairs that should always be sorted - -- lexically by the symbol. - -- The constructor is exported here (because this is an internal module) but - -- should not be exported elsewhere. - --- | The kind of elements of rows. Each element is a label and its associated type. -data LT a = Symbol :-> a - - --- | A label -data Label (s :: Symbol) = Label - deriving (Eq) - -instance KnownSymbol s => Show (Label s) where - show = symbolVal - -instance x ≈ y => IsLabel x (Label y) where -#if __GLASGOW_HASKELL__ >= 802 - fromLabel = Label -#else - fromLabel _ = Label -#endif - --- | A helper function for showing labels -show' :: (IsString s, Show a) => a -> s -show' = fromString . show - --- | A helper function to turn a Label directly into 'Text'. -toKey :: forall s. KnownSymbol s => Label s -> Text -toKey = Text.pack . symbolVal - --- | Type level version of 'empty' -type Empty = R '[] - --- | Elements stored in a Row type are usually hidden. -data HideType where - HideType :: a -> HideType - - - -{-------------------------------------------------------------------- - Row operations ---------------------------------------------------------------------} - -infixl 4 .\ {- This comment needed to appease CPP -} --- | Does the row lack (i.e. it does not have) the specified label? -type family (r :: Row k) .\ (l :: Symbol) :: Constraint where - R r .\ l = LacksR l r r - --- | Type level Row extension -type family Extend (l :: Symbol) (a :: k) (r :: Row k) :: Row k where - Extend l a (R x) = R (Inject (l :-> a) x) - --- | Type level Row modification -type family Modify (l :: Symbol) (a :: k) (r :: Row k) :: Row k where - Modify l a (R ρ) = R (ModifyR l a ρ) - --- | Type level row renaming -type family Rename (l :: Symbol) (l' :: Symbol) (r :: Row k) :: Row k where - Rename l l' r = Extend l' (r .! l) (r .- l) - -infixl 5 .! --- | Type level label fetching -type family (r :: Row k) .! (t :: Symbol) :: k where - R r .! l = Get l r - -infixl 6 .- --- | Type level Row element removal -type family (r :: Row k) .- (s :: Symbol) :: Row k where - R r .- l = R (Remove l r) - -infixl 6 .\\ {- This comment needed to appease CPP -} --- | Type level Row difference. That is, @l '.\\' r@ is the row remaining after --- removing any matching elements of @r@ from @l@. -type family (l :: Row k) .\\ (r :: Row k) :: Row k where - R l .\\ R r = R (Diff l r) - --- $merges --- == Various row-type merges --- The difference between '.+' (read "append"), '.\/' (read "min-join"), and --- '.\\' (read "const-union") comes down to how duplicates are handled. --- In '.+', the two given row-types must be entirely unique. Even the same --- entry in both row-types is forbidden. In '.\/', this final restriction is --- relaxed, allowing two row-types that have no conflicts to be merged in the --- logical way. The '.\\' operator is the most liberal, allowing any two row-types --- to be merged together, and whenever there is a conflict, favoring the left argument. --- --- As examples of use: --- --- - '.+' is used when appending two records, assuring that those two records are --- entirely disjoint. --- --- - '.\/' is used when diversifying a variant, allowing some extension to the --- row-type so long as no original types have changed. --- --- - './/' is used when doing record overwrite, allowing data in a record to --- totally overwrite what was previously there. - -infixl 6 .+ --- | Type level Row append -type family (l :: Row k) .+ (r :: Row k) :: Row k where - R l .+ R r = R (Merge l r) - -infixl 6 .\/ --- | The minimum join of the two rows. -type family (l :: Row k) .\/ (r :: Row k) where - R l .\/ R r = R (MinJoinR l r) - -infixl 6 .// --- | The overwriting union, where the left row overwrites the types of the right --- row where the labels overlap. -type family (l :: Row k) .// (r :: Row k) where - R l .// R r = R (ConstUnionR l r) - - -{-------------------------------------------------------------------- - Syntactic sugar for record operations ---------------------------------------------------------------------} --- | Alias for '.\'. It is a class rather than an alias, so that --- it can be partially applied. -class Lacks (l :: Symbol) (r :: Row *) -instance (r .\ l) => Lacks l r - - --- | Alias for @(r .! l) ≈ a@. It is a class rather than an alias, so that --- it can be partially applied. -class (r .! l ≈ a) => HasType l a r -instance (r .! l ≈ a) => HasType l a r - --- | A type level way to create a singleton Row. -infix 7 .== -type (l :: Symbol) .== (a :: k) = Extend l a Empty - - -{-------------------------------------------------------------------- - Constrained record operations ---------------------------------------------------------------------} - --- | Any structure over a row in which every element is similarly constrained can --- be metamorphized into another structure over the same row. -class Forall (r :: Row k) (c :: k -> Constraint) where - -- | A metamorphism is an unfold followed by a fold. This one is for - -- product-like row-types (e.g. Rec). - metamorph :: forall (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). - Proxy h - -> (f Empty -> g Empty) - -- ^ The way to transform the empty element - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> f ('R (ℓ :-> τ ': ρ)) -> (h τ, f ('R ρ))) - -- ^ The unfold - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> h τ -> g ('R ρ) -> g ('R (ℓ :-> τ ': ρ))) - -- ^ The fold - -> f r -- ^ The input structure - -> g r - - -- | A metamorphism is an unfold followed by a fold. This one is for - -- sum-like row-types (e.g. Var). - metamorph' :: forall (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). - Proxy h - -> (f Empty -> g Empty) - -- ^ The way to transform the empty element - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> f ('R (ℓ :-> τ ': ρ)) -> Either (h τ) (f ('R ρ))) - -- ^ The unfold - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> Either (h τ) (g ('R ρ)) -> g ('R (ℓ :-> τ ': ρ))) - -- ^ The fold - -> f r -- ^ The input structure - -> g r - --- | This data type is used to for its ability to existentially bind a type --- variable. Particularly, it says that for the type 'a', there exists a 't' --- such that 'a ~ f t' and 'c t' holds. -data As c f a where - As :: forall c f a t. (a ~ f t, c t) => As c f a - --- | A class to capture the idea of 'As' so that it can be partially applied in --- a context. -class IsA c f a where - as :: As c f a - -instance c a => IsA c f (f a) where - as = As - --- | An internal type used by the 'metamorph' in 'mapForall'. -newtype MapForall c f (r :: Row k) = MapForall { unMapForall :: Dict (Forall (Map f r) (IsA c f)) } - --- | This allows us to derive a `Forall (Map f r) ..` from a `Forall r ..`. -mapForall :: forall f c ρ. Forall ρ c :- Forall (Map f ρ) (IsA c f) -mapForall = Sub $ unMapForall $ metamorph @_ @ρ @c @(Const ()) @(MapForall c f) @(Const ()) Proxy empty uncons cons $ Const () - where empty :: Const () Empty -> MapForall c f Empty - empty _ = MapForall Dict - - uncons :: forall l t r. (KnownSymbol l, c t) - => Label l -> Const () ('R (l :-> t ': r)) -> (Const () t, Const () ('R r)) - uncons _ _ = (Const (), Const ()) - - cons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Const () τ -> MapForall c f ('R ρ) - -> MapForall c f ('R (ℓ :-> τ ': ρ)) - cons _ _ (MapForall Dict) = MapForall Dict - --- | Map preserves uniqueness of labels. -uniqueMap :: forall f ρ. AllUniqueLabels ρ :- AllUniqueLabels (Map f ρ) -uniqueMap = Sub $ UNSAFE.unsafeCoerce @(Dict Unconstrained) Dict - --- | Allow any 'Forall` over a row-type, be usable for 'Unconstrained1'. -freeForall :: forall r c. Forall r c :- Forall r Unconstrained1 -freeForall = Sub $ UNSAFE.unsafeCoerce @(Dict (Forall r c)) Dict - --- | This allows us to derive `Map f r .! l ≈ f t` from `r .! l ≈ t` -mapHas :: forall f r l t. (r .! l ≈ t) :- (Map f r .! l ≈ f t) -mapHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(r .! l ≈ t) - -instance Forall (R '[]) c where - {-# INLINE metamorph #-} - metamorph _ empty _ _ = empty - {-# INLINE metamorph' #-} - metamorph' _ empty _ _ = empty - -instance (KnownSymbol ℓ, c τ, Forall ('R ρ) c) => Forall ('R (ℓ :-> τ ': ρ) :: Row k) c where - metamorph :: forall (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). - Proxy h - -> (f Empty -> g Empty) - -- ^ The way to transform the empty element - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> f ('R (ℓ :-> τ ': ρ)) -> (h τ, f ('R ρ))) - -- ^ The unfold - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> h τ -> g ('R ρ) -> g ('R (ℓ :-> τ ': ρ))) - -- ^ The fold - -> f ('R (ℓ :-> τ ': ρ)) -- ^ The input structure - -> g ('R (ℓ :-> τ ': ρ)) - {-# INLINE metamorph #-} - metamorph _ empty uncons cons r = cons Label t $ metamorph @_ @('R ρ) @c @_ @_ @h Proxy empty uncons cons r' - where (t, r') = uncons Label r - metamorph' :: forall (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). - Proxy h - -> (f Empty -> g Empty) - -- ^ The way to transform the empty element - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> f ('R (ℓ :-> τ ': ρ)) -> Either (h τ) (f ('R ρ))) - -- ^ The unfold - -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ) => Label ℓ -> Either (h τ) (g ('R ρ)) -> g ('R (ℓ :-> τ ': ρ))) - -- ^ The fold - -> f ('R (ℓ :-> τ ': ρ)) -- ^ The input structure - -> g ('R (ℓ :-> τ ': ρ)) - {-# INLINE metamorph' #-} - metamorph' _ empty uncons cons r = cons Label $ metamorph' @_ @('R ρ) @c @_ @_ @h Proxy empty uncons cons <$> uncons Label r - --- | Any structure over two rows in which the elements of each row satisfy some --- constraints can be metamorphized into another structure over both of the --- rows. -class BiForall (r1 :: Row k1) (r2 :: Row k2) (c :: k1 -> k2 -> Constraint) where - -- | A metamorphism is a fold followed by an unfold. This one is for - -- product-like row-types. - biMetamorph :: forall (f :: Row k1 -> Row k2 -> *) (g :: Row k1 -> Row k2 -> *) - (h :: k1 -> k2 -> *). - Proxy h - -> (f Empty Empty -> g Empty Empty) - -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) - => Label ℓ - -> f ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2)) - -> (h τ1 τ2, f ('R ρ1) ('R ρ2))) - -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) - => Label ℓ -> h τ1 τ2 -> g ('R ρ1) ('R ρ2) -> g ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2))) - -> f r1 r2 -> g r1 r2 - - -- | A metamorphism is a fold followed by an unfold. This one is for - -- sum-like row-types. - biMetamorph' :: forall (f :: Row k1 -> Row k2 -> *) (g :: Row k1 -> Row k2 -> *) - (h :: k1 -> k2 -> *). - Proxy h - -> (f Empty Empty -> g Empty Empty) - -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) - => Label ℓ - -> f ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2)) - -> Either (h τ1 τ2) (f ('R ρ1) ('R ρ2))) - -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) - => Label ℓ -> Either (h τ1 τ2) (g ('R ρ1) ('R ρ2)) -> g ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2))) - -> f r1 r2 -> g r1 r2 - -instance BiForall (R '[]) (R '[]) c1 where - {-# INLINE biMetamorph #-} - biMetamorph _ empty _ _ = empty - biMetamorph' _ empty _ _ = empty - -instance (KnownSymbol ℓ, c τ1 τ2, BiForall ('R ρ1) ('R ρ2) c) - => BiForall ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2)) c where - {-# INLINE biMetamorph #-} - biMetamorph h empty uncons cons r = cons (Label @ℓ) t $ biMetamorph @_ @_ @('R ρ1) @('R ρ2) @c h empty uncons cons r' - where (t, r') = uncons (Label @ℓ) r - {-# INLINE biMetamorph' #-} - biMetamorph' h empty uncons cons r = - cons (Label @ℓ) $ biMetamorph' @_ @_ @('R ρ1) @('R ρ2) @c h empty uncons cons <$> uncons (Label @ℓ) r - --- | A null constraint -class Unconstrained -instance Unconstrained - --- | A null constraint of one argument -class Unconstrained1 a -instance Unconstrained1 a - --- | A null constraint of two arguments -class Unconstrained2 a b -instance Unconstrained2 a b - --- | A pair of constraints -class (c1 x, c2 y) => BiConstraint c1 c2 x y -instance (c1 x, c2 y) => BiConstraint c1 c2 x y - --- | The labels in a Row. -type family Labels (r :: Row a) where - Labels (R '[]) = '[] - Labels (R (l :-> a ': xs)) = l ': Labels (R xs) - --- | Return a list of the labels in a row type. -labels :: forall ρ c s. (IsString s, Forall ρ c) => [s] -labels = getConst $ metamorph @_ @ρ @c @(Const ()) @(Const [s]) @(Const ()) Proxy (const $ Const []) doUncons doCons (Const ()) - where doUncons _ _ = (Const (), Const ()) - doCons l _ (Const c) = Const $ show' l : c - --- | Return a list of the labels in a row type and is specialized to the 'Unconstrained1' constraint. -labels' :: forall ρ s. (IsString s, Forall ρ Unconstrained1) => [s] -labels' = labels @ρ @Unconstrained1 - - -{-------------------------------------------------------------------- - Convenient type families and classes ---------------------------------------------------------------------} - --- | A convenient way to provide common, easy constraints -type WellBehaved ρ = (Forall ρ Unconstrained1, AllUniqueLabels ρ) - --- | Are all of the labels in this Row unique? -type family AllUniqueLabels (r :: Row k) :: Constraint where - AllUniqueLabels (R r) = AllUniqueLabelsR r - -type family AllUniqueLabelsR (r :: [LT k]) :: Constraint where - AllUniqueLabelsR '[] = Unconstrained - AllUniqueLabelsR '[l :-> a] = Unconstrained - AllUniqueLabelsR (l :-> a ': l :-> b ': _) = TypeError - (TL.Text "The label " :<>: ShowType l :<>: TL.Text " is not unique." - :$$: TL.Text "It is assigned to both " :<>: ShowType a :<>: TL.Text " and " :<>: ShowType b) - AllUniqueLabelsR (l :-> a ': l' :-> b ': r) = AllUniqueLabelsR (l' :-> b ': r) - --- | Is the first row a subset of the second? -type family Subset (r1 :: Row k) (r2 :: Row k) :: Constraint where - Subset (R r1) (R r2) = SubsetR r1 r2 - -type family SubsetR (r1 :: [LT k]) (r2 :: [LT k]) :: Constraint where - SubsetR '[] _ = Unconstrained - SubsetR x '[] = TypeError (TL.Text "One row-type is not a subset of the other." - :$$: TL.Text "The first contains the bindings " :<>: ShowType x - :<>: TL.Text " while the second does not.") - SubsetR (l :-> a ': x) (l :-> a ': y) = SubsetR x y - SubsetR (l :-> a ': x) (l :-> b ': y) = - TypeError (TL.Text "One row-type is not a subset of the other." - :$$: TL.Text "The first assigns the label " :<>: ShowType l :<>: TL.Text " to " - :<>: ShowType a :<>: TL.Text " while the second assigns it to " :<>: ShowType b) - SubsetR (hl :-> al ': tl) (hr :-> ar ': tr) = - Ifte (hl <=.? hr) - (TypeError (TL.Text "One row-type is not a subset of the other." - :$$: TL.Text "The first assigns the label " :<>: ShowType hl :<>: TL.Text " to " - :<>: ShowType al :<>: TL.Text " while the second has no assignment for it.")) - (SubsetR (hl :-> al ': tl) tr) - --- | A type synonym for disjointness. -type Disjoint l r = ( WellBehaved l - , WellBehaved r - , Subset l (l .+ r) - , Subset r (l .+ r) - , l .+ r .\\ l ≈ r - , l .+ r .\\ r ≈ l) - --- | Map a type level function over a Row. -type family Map (f :: a -> b) (r :: Row a) :: Row b where - Map f (R r) = R (MapR f r) - -type family MapR (f :: a -> b) (r :: [LT a]) :: [LT b] where - MapR f '[] = '[] - MapR f (l :-> v ': t) = l :-> f v ': MapR f t - --- | Take two rows with the same labels, and apply the type operator from the --- first row to the type of the second. -type family Ap (fs :: Row (a -> b)) (r :: Row a) :: Row b where - Ap (R fs) (R r) = R (ApR fs r) - -type family ApR (fs :: [LT (a -> b)]) (r :: [LT a]) :: [LT b] where - ApR '[] '[] = '[] - ApR (l :-> f ': tf) (l :-> v ': tv) = l :-> f v ': ApR tf tv - ApR _ _ = TypeError (TL.Text "Row types with different label sets cannot be App'd together.") - --- | Zips two rows together to create a Row of the pairs. --- The two rows must have the same set of labels. -type family Zip (r1 :: Row *) (r2 :: Row *) where - Zip (R r1) (R r2) = R (ZipR r1 r2) - -type family ZipR (r1 :: [LT *]) (r2 :: [LT *]) where - ZipR '[] '[] = '[] - ZipR (l :-> t1 ': r1) (l :-> t2 ': r2) = - l :-> (t1, t2) ': ZipR r1 r2 - ZipR (l :-> t1 ': r1) _ = TypeError (TL.Text "Row types with different label sets cannot be zipped" - :$$: TL.Text "For one, the label " :<>: ShowType l :<>: TL.Text " is not in both lists.") - ZipR '[] (l :-> t ': r) = TypeError (TL.Text "Row types with different label sets cannot be zipped" - :$$: TL.Text "For one, the label " :<>: ShowType l :<>: TL.Text " is not in both lists.") - -type family Inject (l :: LT k) (r :: [LT k]) where - Inject (l :-> t) '[] = (l :-> t ': '[]) - Inject (l :-> t) (l :-> t' ': x) = TypeError (TL.Text "Cannot inject a label into a row type that already has that label" - :$$: TL.Text "The label " :<>: ShowType l :<>: TL.Text " was already assigned the type " - :<>: ShowType t' :<>: TL.Text " and is now trying to be assigned the type " - :<>: ShowType t :<>: TL.Text ".") - Inject (l :-> t) (l' :-> t' ': x) = - Ifte (l <=.? l') - (l :-> t ': l' :-> t' ': x) - (l' :-> t' ': Inject (l :-> t) x) - --- | Type level Row modification helper -type family ModifyR (l :: Symbol) (a :: k) (ρ :: [LT k]) :: [LT k] where - ModifyR l a (l :-> a' ': ρ) = l :-> a ': ρ - ModifyR l a (l' :-> a' ': ρ) = l' :-> a' ': ModifyR l a ρ - ModifyR l a '[] = TypeError (TL.Text "Tried to modify the label " :<>: ShowType l - :<>: TL.Text ", but it does not appear in the row-type.") - -type family Ifte (c :: Bool) (t :: k) (f :: k) where - Ifte True t f = t - Ifte False t f = f - -type family Get (l :: Symbol) (r :: [LT k]) where - Get l '[] = TypeError (TL.Text "No such field: " :<>: ShowType l) - Get l (l :-> t ': x) = t - Get l (l' :-> t ': x) = Get l x - -type family Remove (l :: Symbol) (r :: [LT k]) where - Remove l r = RemoveT l r r - -type family RemoveT (l :: Symbol) (r :: [LT k]) (r_orig :: [LT k]) where - RemoveT l (l :-> t ': x) _ = x - RemoveT l (l' :-> t ': x) r = l' :-> t ': RemoveT l x r - RemoveT l '[] r = TypeError (TL.Text "Cannot remove a label that does not occur in the row type." - :$$: TL.Text "The label " :<>: ShowType l :<>: TL.Text " is not in " - :<>: ShowType r) - -type family LacksR (l :: Symbol) (r :: [LT k]) (r_orig :: [LT k]) :: Constraint where - LacksR l '[] _ = Unconstrained - LacksR l (l :-> t ': x) r = TypeError (TL.Text "The label " :<>: ShowType l - :<>: TL.Text " already exists in " :<>: ShowType r) - LacksR l (l' :-> _ ': x) r = Ifte (l <=.? l') Unconstrained (LacksR l x r) - - -type family Merge (l :: [LT k]) (r :: [LT k]) where - Merge '[] r = r - Merge l '[] = l - Merge (h :-> a ': tl) (h :-> a ': tr) = - TypeError (TL.Text "The label " :<>: ShowType h :<>: TL.Text " (of type " - :$$: ShowType a :<>: TL.Text ") has duplicate assignments.") - Merge (h :-> a ': tl) (h :-> b ': tr) = - TypeError (TL.Text "The label " :<>: ShowType h :<>: TL.Text " has conflicting assignments." - :$$: TL.Text "Its type is both " :<>: ShowType a :<>: TL.Text " and " :<>: ShowType b :<>: TL.Text ".") - Merge (hl :-> al ': tl) (hr :-> ar ': tr) = - Ifte (hl <=.? hr) - (hl :-> al ': Merge tl (hr :-> ar ': tr)) - (hr :-> ar ': Merge (hl :-> al ': tl) tr) - -type family MinJoinR (l :: [LT k]) (r :: [LT k]) where - MinJoinR '[] r = r - MinJoinR l '[] = l - MinJoinR (h :-> a ': tl) (h :-> a ': tr) = - (h :-> a ': MinJoinR tl tr) - MinJoinR (h :-> a ': tl) (h :-> b ': tr) = - TypeError (TL.Text "The label " :<>: ShowType h :<>: TL.Text " has conflicting assignments." - :$$: TL.Text "Its type is both " :<>: ShowType a :<>: TL.Text " and " :<>: ShowType b :<>: TL.Text ".") - MinJoinR (hl :-> al ': tl) (hr :-> ar ': tr) = - Ifte (CmpSymbol hl hr == 'LT) - (hl :-> al ': MinJoinR tl (hr :-> ar ': tr)) - (hr :-> ar ': MinJoinR (hl :-> al ': tl) tr) - -type family ConstUnionR (l :: [LT k]) (r :: [LT k]) where - ConstUnionR '[] r = r - ConstUnionR l '[] = l - ConstUnionR (h :-> a ': tl) (h :-> b ': tr) = - (h :-> a ': ConstUnionR tl tr) - ConstUnionR (hl :-> al ': tl) (hr :-> ar ': tr) = - Ifte (CmpSymbol hl hr == 'LT) - (hl :-> al ': ConstUnionR tl (hr :-> ar ': tr)) - (hr :-> ar ': ConstUnionR (hl :-> al ': tl) tr) - - --- | Returns the left list with all of the elements from the right list removed. -type family Diff (l :: [LT k]) (r :: [LT k]) where - Diff '[] r = '[] - Diff l '[] = l - Diff (l :-> al ': tl) (l :-> al ': tr) = Diff tl tr - Diff (hl :-> al ': tl) (hr :-> ar ': tr) = - Ifte (hl <=.? hr) - (hl :-> al ': Diff tl (hr :-> ar ': tr)) - (Diff (hl :-> al ': tl) tr) - --- | There doesn't seem to be a (<=.?) :: Symbol -> Symbol -> Bool, --- so here it is in terms of other ghc-7.8 type functions -type a <=.? b = (CmpSymbol a b == 'LT) - --- | A lower fixity operator for type equality -infix 4 ≈ -type a ≈ b = a ~ b
− Data/Row/Records.hs
@@ -1,706 +0,0 @@------------------------------------------------------------------------------ --- | --- Module : Data.Row.Records --- --- This module implements extensible records using closed type famillies. --- --- See Examples.lhs for examples. --- --- Lists of (label,type) pairs are kept sorted thereby ensuring --- that { x = 0, y = 0 } and { y = 0, x = 0 } have the same type. --- --- In this way we can implement standard type classes such as Show, Eq, Ord and Bounded --- for open records, given that all the elements of the open record satify the constraint. --- ------------------------------------------------------------------------------ - - -module Data.Row.Records - ( - -- * Types and constraints - Label(..) - , KnownSymbol, AllUniqueLabels, WellBehaved - , Rec, Row, Empty, type (≈) - -- * Construction - , empty - , type (.==), (.==), pattern (:==), unSingleton - , default', defaultA - , fromLabels, fromLabelsA, fromLabelsMapA - -- ** Extension - , extend, Extend, Lacks, type (.\) - -- ** Restriction - , type (.-), (.-) - , restrict, split - -- ** Modification - , update, focus, multifocus, Modify, rename, Rename - -- * Query - , HasType, type (.!), (.!) - -- * Combine - -- ** Disjoint union - , type (.+), (.+), Disjoint, pattern (:+) - -- ** Overwrite - , type (.//), (.//) - -- * Native Conversion - -- $native - , fromNative, toNative, toNativeGeneral - , FromNative, ToNative, ToNativeGeneral - , NativeRow - -- * Dynamic Conversion - , toDynamicMap, fromDynamicMap - -- * Row operations - -- ** Map - , Map, map, map', mapF - , transform, transform' - -- ** Fold - , BiForall, Forall, erase, eraseWithLabels, eraseZip, eraseToHashMap - -- ** Zip - , Zip, zip - -- ** Sequence - , sequence, sequence' - -- ** Compose - -- $compose - , compose, uncompose - , compose', uncompose' - -- ** Labels - , labels, labels' - -- ** Coerce - , coerceRec - -- ** UNSAFE operations - , unsafeRemove, unsafeInjectFront - ) -where - -import Prelude hiding (map, sequence, zip) - -import Control.DeepSeq (NFData(..), deepseq) - -import Data.Coerce -import Data.Constraint ((\\)) -import Data.Dynamic -import Data.Functor.Compose -import Data.Functor.Const -import Data.Functor.Identity -import Data.Functor.Product -import Data.Generics.Product.Fields (HasField(..), HasField'(..)) -import Data.Hashable -import Data.HashMap.Lazy (HashMap) -import qualified Data.HashMap.Lazy as M -import qualified Data.List as L -import Data.Monoid (Endo(..), appEndo) -import Data.Proxy -import Data.String (IsString) -import Data.Text (Text) - -import qualified GHC.Generics as G -import GHC.TypeLits - -import Unsafe.Coerce - -import Data.Row.Internal - - -{-------------------------------------------------------------------- - Open records ---------------------------------------------------------------------} --- | A record with row r. -newtype Rec (r :: Row *) where - OR :: HashMap Text HideType -> Rec r - -instance Forall r Show => Show (Rec r) where - showsPrec p r = - case eraseWithLabels @Show (showsPrec 7) r of - [] -> - showString "empty" - xs -> - showParen - (p > 6) - (appEndo $ foldMap Endo (L.intersperse (showString " .+ ") (L.map binds xs))) - where - binds (label, value) = - showChar '#' . - showString label . - showString " .== " . - value - -instance Forall r Eq => Eq (Rec r) where - r == r' = and $ eraseZip @Eq (==) r r' - -instance (Forall r Eq, Forall r Ord) => Ord (Rec r) where - compare m m' = cmp $ eraseZip @Ord compare m m' - where cmp l | [] <- l' = EQ - | a : _ <- l' = a - where l' = dropWhile (== EQ) l - -instance (Forall r Bounded, AllUniqueLabels r) => Bounded (Rec r) where - minBound = default' @Bounded minBound - maxBound = default' @Bounded maxBound - -instance Forall r NFData => NFData (Rec r) where - rnf r = getConst $ metamorph @_ @r @NFData @Rec @(Const ()) @Identity Proxy empty doUncons doCons r - where empty = const $ Const () - doUncons l r = (Identity $ r .! l, unsafeRemove l r) - doCons _ x r = deepseq x $ deepseq r $ Const () - --- | The empty record -empty :: Rec Empty -empty = OR M.empty - --- | The singleton record -infix 7 .== -(.==) :: KnownSymbol l => Label l -> a -> Rec (l .== a) -l .== a = extend l a empty - --- | A pattern for the singleton record; can be used to both destruct a record --- when in a pattern position or construct one in an expression position. -{-# COMPLETE (:==) #-} -infix 7 :== -pattern (:==) :: forall l a. KnownSymbol l => Label l -> a -> Rec (l .== a) -pattern l :== a <- (unSingleton @l @a -> (l, a)) where - (:==) l a = l .== a - --- | Turns a singleton record into a pair of the label and value. -unSingleton :: forall l a. KnownSymbol l => Rec (l .== a) -> (Label l, a) -unSingleton r = (l, r .! l) where l = Label @l - -{-------------------------------------------------------------------- - Basic record operations ---------------------------------------------------------------------} - - --- | Record extension. The row may already contain the label, --- in which case the origin value can be obtained after restriction ('.-') with --- the label. -extend :: forall a l r. KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) -extend (toKey -> l) a (OR m) = OR $ M.insert l (HideType a) m - --- | Update the value associated with the label. -update :: (KnownSymbol l, r .! l ≈ a) => Label l -> a -> Rec r -> Rec r -update (toKey -> l) a (OR m) = OR $ M.adjust f l m where f = const (HideType a) - --- | Focus on the value associated with the label. -focus :: - ( KnownSymbol l - , r' .! l ≈ b - , r .! l ≈ a - , r' ~ Modify l b r - , r ~ Modify l a r' - , Functor f) - => Label l -> (a -> f b) -> Rec r -> f (Rec r') -focus (toKey -> l) f (OR m) = case m M.! l of - HideType x -> OR . flip (M.insert l) m . HideType <$> f (unsafeCoerce x) - --- | Focus on a sub-record -multifocus :: forall u v r f. - ( Functor f - , Disjoint u r - , Disjoint v r) - => (Rec u -> f (Rec v)) -> Rec (u .+ r) -> f (Rec (v .+ r)) -multifocus f (u :+ r) = (.+ r) <$> f u - --- | Rename a label. -rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) -rename (toKey -> l) (toKey -> l') (OR m) = OR $ M.insert l' (m M.! l) $ M.delete l m - --- | Record selection -(.!) :: KnownSymbol l => Rec r -> Label l -> r .! l -OR m .! (toKey -> a) = case m M.! a of - HideType x -> unsafeCoerce x - -infixl 6 .- --- | Record restriction. Remove the label l from the record. -(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r .- l) --- OR m .- _ = OR m -OR m .- (toKey -> a) = OR $ M.delete a m - --- | Record disjoint union (commutative) -infixl 6 .+ -(.+) :: Rec l -> Rec r -> Rec (l .+ r) -OR l .+ OR r = OR $ M.unionWith (error "Impossible") l r - --- | Record overwrite. --- --- The operation @r .// r'@ creates a new record such that: --- --- - Any label that is in both @r@ and @r'@ is in the resulting record with the --- type and value given by the fields in @r@, --- --- - Any label that is only found in @r@ is in the resulting record. --- --- - Any label that is only found in @r'@ is in the resulting record. --- --- This can be thought of as @r@ "overwriting" @r'@. -(.//) :: Rec r -> Rec r' -> Rec (r .// r') -OR l .// OR r = OR $ M.union l r - --- | A pattern version of record union, for use in pattern matching. -{-# COMPLETE (:+) #-} -infixl 6 :+ -pattern (:+) :: forall l r. Disjoint l r => Rec l -> Rec r -> Rec (l .+ r) -pattern l :+ r <- (split @l -> (l, r)) where - (:+) l r = l .+ r - --- | Split a record into two sub-records. -split :: forall s r. (Forall s Unconstrained1, Subset s r) - => Rec r -> (Rec s, Rec (r .\\ s)) -split (OR m) = (OR $ M.intersection m labelMap, OR $ M.difference m labelMap) - where labelMap = M.fromList $ L.zip (labels @s @Unconstrained1) (repeat ()) - --- | Arbitrary record restriction. Turn a record into a subset of itself. -restrict :: forall r r'. (Forall r Unconstrained1, Subset r r') => Rec r' -> Rec r -restrict = fst . split - --- | Removes a label from the record but does not remove the underlying value. --- --- This is faster than regular record removal ('.-') but should only be used when --- either: the record will never be merged with another record again, or a new --- value will soon be placed into the record at this label (as in, an 'update' --- that is split over two commands). --- --- If the resulting record is then merged (with '.+') with another record that --- contains a value at that label, an "impossible" error will occur. -unsafeRemove :: KnownSymbol l => Label l -> Rec r -> Rec (r .- l) -unsafeRemove _ (OR m) = OR m - - -{-------------------------------------------------------------------- - Folds and maps ---------------------------------------------------------------------} --- An easier type synonym for a pair where both elements are the same type. -type IPair = Product Identity Identity - --- Construct an IPair. -iPair :: τ -> τ -> IPair τ -iPair = (. Identity) . Pair . Identity - --- Destruct an IPair. Easily used with ViewPatterns. -unIPair :: IPair τ -> (τ, τ) -unIPair (Pair (Identity x) (Identity y)) = (x,y) - - --- | A standard fold -erase :: forall c ρ b. Forall ρ c => (forall a. c a => a -> b) -> Rec ρ -> [b] -erase f = fmap (snd @String) . eraseWithLabels @c f - --- | A fold with labels -eraseWithLabels :: forall c ρ s b. (Forall ρ c, IsString s) => (forall a. c a => a -> b) -> Rec ρ -> [(s,b)] -eraseWithLabels f = getConst . metamorph @_ @ρ @c @Rec @(Const [(s,b)]) @Identity Proxy doNil doUncons doCons - where doNil _ = Const [] - doUncons l r = (Identity $ r .! l, unsafeRemove l r) - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Identity τ -> Const [(s,b)] ('R ρ) -> Const [(s,b)] ('R (ℓ :-> τ ': ρ)) - doCons l (Identity x) (Const c) = Const $ (show' l, f x) : c - --- | A fold over two row type structures at once -eraseZip :: forall c ρ b. Forall ρ c => (forall a. c a => a -> a -> b) -> Rec ρ -> Rec ρ -> [b] -eraseZip f x y = getConst $ metamorph @_ @ρ @c @(Product Rec Rec) @(Const [b]) @IPair Proxy (const $ Const []) doUncons doCons (Pair x y) - where doUncons l (Pair r1 r2) = (iPair a b, Pair r1' r2') - where (a, r1') = (r1 .! l, unsafeRemove l r1) - (b, r2') = (r2 .! l, unsafeRemove l r2) - doCons :: forall ℓ τ ρ. c τ - => Label ℓ -> IPair τ -> Const [b] ('R ρ) -> Const [b] ('R (ℓ :-> τ ': ρ)) - doCons _ (unIPair -> x) (Const c) = Const $ uncurry f x : c - --- | Turns a record into a 'HashMap' from values representing the labels to --- the values of the record. -eraseToHashMap :: forall c r s b. (IsString s, Eq s, Hashable s, Forall r c) => - (forall a . c a => a -> b) -> Rec r -> HashMap s b -eraseToHashMap f r = M.fromList $ eraseWithLabels @c f r - --- | RMap is used internally as a type level lambda for defining record maps. -newtype RMap (f :: * -> *) (ρ :: Row *) = RMap { unRMap :: Rec (Map f ρ) } -newtype RMap2 (f :: * -> *) (g :: * -> *) (ρ :: Row *) = RMap2 { unRMap2 :: Rec (Map f (Map g ρ)) } - --- | A function to map over a record given a constraint. -map :: forall c f r. Forall r c => (forall a. c a => a -> f a) -> Rec r -> Rec (Map f r) -map f = unRMap . metamorph @_ @r @c @Rec @(RMap f) @Identity Proxy doNil doUncons doCons - where - doNil _ = RMap empty - doUncons l r = (Identity $ r .! l, unsafeRemove l r) - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Identity τ -> RMap f ('R ρ) -> RMap f ('R (ℓ :-> τ ': ρ)) - doCons l (Identity v) (RMap r) = RMap (unsafeInjectFront l (f v) r) - -newtype RFMap (g :: k1 -> k2) (ϕ :: Row (k2 -> *)) (ρ :: Row k1) = RFMap { unRFMap :: Rec (Ap ϕ (Map g ρ)) } -newtype RecAp (ϕ :: Row (k -> *)) (ρ :: Row k) = RecAp (Rec (Ap ϕ ρ)) -newtype App (f :: k -> *) (a :: k) = App (f a) - --- | A function to map over a Ap record given constraints. -mapF :: forall k c g (ϕ :: Row (k -> *)) (ρ :: Row k). BiForall ϕ ρ c - => (forall f a. (c f a) => f a -> f (g a)) - -> Rec (Ap ϕ ρ) - -> Rec (Ap ϕ (Map g ρ)) -mapF f = unRFMap . biMetamorph @_ @_ @ϕ @ρ @c @RecAp @(RFMap g) @App Proxy doNil doUncons doCons . RecAp - where - doNil _ = RFMap empty - doUncons l (RecAp r) = (App $ r .! l, RecAp $ unsafeRemove l r) - doCons :: forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2) - => Label ℓ -> App τ1 τ2 -> RFMap g ('R ρ1) ('R ρ2) -> RFMap g ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2)) - doCons l (App v) (RFMap r) = RFMap (unsafeInjectFront l (f @τ1 @τ2 v) r) - --- | A function to map over a record given no constraint. -map' :: forall f r. Forall r Unconstrained1 => (forall a. a -> f a) -> Rec r -> Rec (Map f r) -map' = map @Unconstrained1 - --- | Lifts a natural transformation over a record. In other words, it acts as a --- record transformer to convert a record of @f a@ values to a record of @g a@ --- values. If no constraint is needed, instantiate the first type argument with --- 'Unconstrained1' or use 'transform''. -transform :: forall c r (f :: * -> *) (g :: * -> *). Forall r c => (forall a. c a => f a -> g a) -> Rec (Map f r) -> Rec (Map g r) -transform f = unRMap . metamorph @_ @r @c @(RMap f) @(RMap g) @f Proxy doNil doUncons doCons . RMap - where - doNil _ = RMap empty - doUncons l (RMap r) = (r .! l, RMap $ unsafeRemove l r) - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> f τ -> RMap g ('R ρ) -> RMap g ('R (ℓ :-> τ ': ρ)) - doCons l v (RMap r) = RMap (unsafeInjectFront l (f v) r) - --- | A version of 'transform' for when there is no constraint. -transform' :: forall r (f :: * -> *) (g :: * -> *). Forall r Unconstrained1 => (forall a. f a -> g a) -> Rec (Map f r) -> Rec (Map g r) -transform' = transform @Unconstrained1 @r - --- | A version of 'sequence' in which the constraint for 'Forall' can be chosen. -sequence' :: forall f r c. (Forall r c, Applicative f) - => Rec (Map f r) -> f (Rec r) -sequence' = getCompose . metamorph @_ @r @c @(RMap f) @(Compose f Rec) @f Proxy doNil doUncons doCons . RMap - where - doNil _ = Compose (pure empty) - doUncons l (RMap r) = (r .! l, RMap $ unsafeRemove l r) - doCons l fv (Compose fr) = Compose $ unsafeInjectFront l <$> fv <*> fr - --- | Applicative sequencing over a record. -sequence :: forall f r. (Forall r Unconstrained1, Applicative f) - => Rec (Map f r) -> f (Rec r) -sequence = sequence' @_ @_ @Unconstrained1 - --- $compose --- We can easily convert between mapping two functors over the types of a row --- and mapping the composition of the two functors. The following two functions --- perform this composition with the gaurantee that: --- --- >>> compose . uncompose = id --- --- >>> uncompose . compose = id - --- | A version of 'compose' in which the constraint for 'Forall' can be chosen. -compose' :: forall c (f :: * -> *) (g :: * -> *) (r :: Row *) . Forall r c - => Rec (Map f (Map g r)) -> Rec (Map (Compose f g) r) -compose' = unRMap . metamorph @_ @r @c @(RMap2 f g) @(RMap (Compose f g)) @(Compose f g) Proxy doNil doUncons doCons . RMap2 - where - doNil _ = RMap empty - doUncons l (RMap2 r) = (Compose $ r .! l, RMap2 $ unsafeRemove l r) - doCons l v (RMap r) = RMap $ unsafeInjectFront l v r - --- | Convert from a record where two functors have been mapped over the types to --- one where the composition of the two functors is mapped over the types. -compose :: forall (f :: * -> *) (g :: * -> *) r . Forall r Unconstrained1 - => Rec (Map f (Map g r)) -> Rec (Map (Compose f g) r) -compose = compose' @Unconstrained1 @f @g @r - --- | A version of 'uncompose' in which the constraint for 'Forall' can be chosen. -uncompose' :: forall c (f :: * -> *) (g :: * -> *) r . Forall r c - => Rec (Map (Compose f g) r) -> Rec (Map f (Map g r)) -uncompose' = unRMap2 . metamorph @_ @r @c @(RMap (Compose f g)) @(RMap2 f g) @(Compose f g) Proxy doNil doUncons doCons . RMap - where - doNil _ = RMap2 empty - doUncons l (RMap r) = (r .! l, RMap $ unsafeRemove l r) - doCons l (Compose v) (RMap2 r) = RMap2 $ unsafeInjectFront l v r - --- | Convert from a record where the composition of two functors have been mapped --- over the types to one where the two functors are mapped individually one at a --- time over the types. -uncompose :: forall (f :: * -> *) (g :: * -> *) r . Forall r Unconstrained1 - => Rec (Map (Compose f g) r) -> Rec (Map f (Map g r)) -uncompose = uncompose' @Unconstrained1 @f @g @r - - --- | Coerce a record to a coercible representation. The 'BiForall' in the context --- indicates that the type of every field in @r1@ can be coerced to the type of --- the corresponding fields in @r2@. --- --- Internally, this is implemented just with `unsafeCoerce`, but we provide the --- following implementation as a proof: --- --- > newtype ConstR a b = ConstR (Rec a) --- > newtype FlipConstR a b = FlipConstR { unFlipConstR :: Rec b } --- > coerceRec = unFlipConstR . biMetamorph @_ @_ @r1 @r2 @Coercible @ConstR @FlipConstR @Const Proxy doNil doUncons doCons . ConstR --- > where --- > doNil _ = FlipConstR empty --- > doUncons l (ConstR r) = (Const (r .! l), ConstR (unsafeRemove l r)) --- > doCons l (Const v) (FlipConstR r) = FlipConstR $ unsafeInjectFront l (coerce v) r -coerceRec :: forall r1 r2. BiForall r1 r2 Coercible => Rec r1 -> Rec r2 -coerceRec = unsafeCoerce - - --- | RZipPair is used internally as a type level lambda for zipping records. -newtype RecPair (ρ1 :: Row *) (ρ2 :: Row *) = RecPair (Rec ρ1, Rec ρ2) -newtype RZipPair (ρ1 :: Row *) (ρ2 :: Row *) = RZipPair { unRZipPair :: Rec (Zip ρ1 ρ2) } - --- | Zips together two records that have the same set of labels. -zip :: forall r1 r2. BiForall r1 r2 Unconstrained2 => Rec r1 -> Rec r2 -> Rec (Zip r1 r2) -zip r1 r2 = unRZipPair $ biMetamorph @_ @_ @r1 @r2 @Unconstrained2 @RecPair @RZipPair @(,) Proxy doNil doUncons doCons $ RecPair (r1, r2) - where - doNil _ = RZipPair empty - doUncons l (RecPair (r1, r2)) = ((r1 .! l, r2 .! l), RecPair (unsafeRemove l r1, unsafeRemove l r2)) - doCons l (v1, v2) (RZipPair r) = RZipPair $ unsafeInjectFront l (v1, v2) r - --- | A helper function for unsafely adding an element to the front of a record. --- This can cause the resulting record to be malformed, for instance, if the record --- already contains labels that are lexicographically before the given label. --- Realistically, this function should only be used when writing calls to 'metamorph'. -unsafeInjectFront :: KnownSymbol l => Label l -> a -> Rec (R r) -> Rec (R (l :-> a ': r)) -unsafeInjectFront (toKey -> a) b (OR m) = OR $ M.insert a (HideType b) m -{-# INLINE unsafeInjectFront #-} - - -{-------------------------------------------------------------------- - Record initialization ---------------------------------------------------------------------} - --- | Initialize a record with a default value at each label. -default' :: forall c ρ. (Forall ρ c, AllUniqueLabels ρ) => (forall a. c a => a) -> Rec ρ -default' v = runIdentity $ defaultA @c $ pure v - --- | Initialize a record with a default value at each label; works over an 'Applicative'. -defaultA :: forall c f ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) - => (forall a. c a => f a) -> f (Rec ρ) -defaultA v = fromLabelsA @c $ pure v - --- | Initialize a record, where each value is determined by the given function over --- the label at that value. -fromLabels :: forall c ρ. (Forall ρ c, AllUniqueLabels ρ) - => (forall l a. (KnownSymbol l, c a) => Label l -> a) -> Rec ρ -fromLabels f = runIdentity $ fromLabelsA @c $ (pure .) f - --- | Initialize a record, where each value is determined by the given function over --- the label at that value. This function works over an 'Applicative'. -fromLabelsA :: forall c f ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) - => (forall l a. (KnownSymbol l, c a) => Label l -> f a) -> f (Rec ρ) -fromLabelsA mk = getCompose $ metamorph @_ @ρ @c @(Const ()) @(Compose f Rec) @(Const ()) Proxy doNil doUncons doCons (Const ()) - where doNil _ = Compose $ pure empty - doUncons _ _ = (Const (), Const ()) - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Const () τ -> Compose f Rec ('R ρ) -> Compose f Rec ('R (ℓ :-> τ ': ρ)) - doCons l _ (Compose r) = Compose $ unsafeInjectFront l <$> mk l <*> r - --- | Initialize a record that is produced by a `Map`. -fromLabelsMapA :: forall c f g ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) - => (forall l a. (KnownSymbol l, c a) => Label l -> f (g a)) -> f (Rec (Map g ρ)) -fromLabelsMapA f = fromLabelsA @(IsA c g) @f @(Map g ρ) inner - \\ mapForall @g @c @ρ - \\ uniqueMap @g @ρ - where inner :: forall l a. (KnownSymbol l, IsA c g a) => Label l -> f a - inner l = case as @c @g @a of As -> f l - - -{-------------------------------------------------------------------- - Dynamic compatibility ---------------------------------------------------------------------} - --- | Converts a 'Rec' into a 'HashMap' of 'Dynamic's. -toDynamicMap :: Forall r Typeable => Rec r -> HashMap Text Dynamic -toDynamicMap = eraseToHashMap @Typeable @_ @Text @Dynamic toDyn - --- | Produces a 'Rec' from a 'HashMap' of 'Dynamic's. -fromDynamicMap :: (AllUniqueLabels r, Forall r Typeable) - => HashMap Text Dynamic -> Maybe (Rec r) -fromDynamicMap m = fromLabelsA @Typeable - $ \ (toKey -> k) -> M.lookup k m >>= fromDynamic - - -{-------------------------------------------------------------------- - Generic instance ---------------------------------------------------------------------} - --- The generic structure we want Recs to have is not the hidden internal one, --- but rather one that appears as a Haskell record. Thus, we can't derive --- Generic automatically. --- --- The following Generic instance creates a representation of a Rec that is --- very similar to a native Haskell record except that the tree of pairs (':*:') --- that it produces will be extremely unbalanced. I don't think this is a problem. --- Furthermore, because we don't want Recs to always have a trailing unit on --- the end, we must have a special case for singleton Recs, which means that --- we can't use metamorph. - -instance GenericRec r => G.Generic (Rec r) where - type Rep (Rec r) = - G.D1 ('G.MetaData "Rec" "Data.Row.Records" "row-types" 'False) - (G.C1 ('G.MetaCons "Rec" 'G.PrefixI 'True) - (RepRec r)) - from = G.M1 . G.M1 . fromRec - to = toRec . G.unM1 . G.unM1 - -class GenericRec r where - type RepRec (r :: Row *) :: * -> * - fromRec :: Rec r -> RepRec r x - toRec :: RepRec r x -> Rec r - -instance GenericRec Empty where - type RepRec (R '[]) = G.U1 - fromRec _ = G.U1 - toRec _ = empty - -instance KnownSymbol name => GenericRec (R '[name :-> t]) where - type RepRec (R (name :-> t ': '[])) = G.S1 - ('G.MetaSel ('Just name) 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) - (G.Rec0 t) - fromRec (_ :== a) = G.M1 (G.K1 a) - toRec (G.M1 (G.K1 a)) = (Label @name) :== a - -instance - ( GenericRec (R (name' :-> t' ': r')) - , KnownSymbol name - ) => GenericRec (R (name :-> t ': (name' :-> t' ': r'))) where - type RepRec (R (name :-> t ': (name' :-> t' ': r'))) = (G.S1 - ('G.MetaSel ('Just name) 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) - (G.Rec0 t)) G.:*: RepRec (R (name' :-> t' ': r')) - fromRec r = G.M1 (G.K1 (r .! Label @name)) G.:*: fromRec (unsafeRemove @name Label r) - toRec (G.M1 (G.K1 a) G.:*: r) = unsafeInjectFront (Label @name) a (toRec r) - -{-------------------------------------------------------------------- - Native data type compatibility ---------------------------------------------------------------------} --- ToNative is shamelessly copied from --- https://www.athiemann.net/2017/07/02/superrecord.html - --- $native --- The 'toNative' and 'fromNative' functions allow one to convert between --- 'Rec's and regular Haskell data types ("native" types) that have a single constructor and any --- number of named fields with the same names and types as the 'Rec'. As expected, --- they compose to form the identity. Alternatively, one may use 'toNativeGeneral', --- which allows fields to be dropped when a record has excess fields compared --- to the native type. Because of this, 'toNativeGeneral' requires a type --- application (although 'fromNative' does not). The only requirement is that --- the native Haskell data type be an instance of 'Generic'. --- --- For example, consider the following simple data type: --- --- >>> data Person = Person { name :: String, age :: Int} deriving (Generic, Show) --- --- Then, we have the following: --- --- >>> toNative @Person $ #name .== "Alice" .+ #age .== 7 .+ #hasDog .== True --- Person {name = "Alice", age = 7} --- >>> fromNative $ Person "Bob" 9 --- { age=9, name="Bob" } - -type family NativeRow t where - NativeRow t = NativeRowG (G.Rep t) - -type family NativeRowG t where - NativeRowG (G.M1 G.D m cs) = NativeRowG cs - NativeRowG (G.M1 G.C m cs) = NativeRowG cs - NativeRowG G.U1 = Empty - NativeRowG (l G.:*: r) = NativeRowG l .+ NativeRowG r - NativeRowG (G.M1 G.S ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) = name .== t - - --- | Conversion helper to turn a Haskell record into a row-types extensible --- record. Note that the native Haskell type must be an instance of 'Generic'. -class FromNativeG a where - fromNative' :: a x -> Rec (NativeRowG a) - -instance FromNativeG cs => FromNativeG (G.D1 m cs) where - fromNative' (G.M1 xs) = fromNative' xs - -instance FromNativeG cs => FromNativeG (G.C1 m cs) where - fromNative' (G.M1 xs) = fromNative' xs - -instance FromNativeG G.U1 where - fromNative' G.U1 = empty - -instance KnownSymbol name => FromNativeG (G.S1 ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) where - fromNative' (G.M1 (G.K1 x)) = (Label @name) .== x - -instance (FromNativeG l, FromNativeG r) => FromNativeG (l G.:*: r) where - fromNative' (x G.:*: y) = fromNative' @l x .+ fromNative' @r y - -type FromNative t = (G.Generic t, FromNativeG (G.Rep t)) - --- | Convert a Haskell record to a row-types Rec. -fromNative :: FromNative t => t -> Rec (NativeRow t) -fromNative = fromNative' . G.from - - --- | Conversion helper to bring a record back into a Haskell type. Note that the --- native Haskell type must be an instance of 'Generic'. -class ToNativeG a where - toNative' :: Rec (NativeRowG a) -> a x - -instance ToNativeG cs => ToNativeG (G.D1 m cs) where - toNative' xs = G.M1 $ toNative' xs - -instance ToNativeG cs => ToNativeG (G.C1 m cs) where - toNative' xs = G.M1 $ toNative' xs - -instance ToNativeG G.U1 where - toNative' _ = G.U1 - -instance (KnownSymbol name) => ToNativeG (G.S1 ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) where - toNative' r = G.M1 $ G.K1 $ r .! (Label @name) - -instance (ToNativeG l, ToNativeG r, Disjoint (NativeRowG l) (NativeRowG r)) - => ToNativeG (l G.:*: r) where - toNative' r = toNative' r1 G.:*: toNative' r2 - where - (r1 :: Rec (NativeRowG l)) :+ (r2 :: Rec (NativeRowG r)) = r - -type ToNative t = (G.Generic t, ToNativeG (G.Rep t)) - --- | Convert a record to an exactly matching native Haskell type. -toNative :: ToNative t => Rec (NativeRow t) -> t -toNative = G.to . toNative' - - - --- | Conversion helper to bring a record back into a Haskell type. Note that the --- native Haskell type must be an instance of 'Generic'. -class ToNativeGeneralG a ρ where - toNativeGeneral' :: Rec ρ -> a x - -instance ToNativeGeneralG cs ρ => ToNativeGeneralG (G.D1 m cs) ρ where - toNativeGeneral' xs = G.M1 $ toNativeGeneral' xs - -instance ToNativeGeneralG cs ρ => ToNativeGeneralG (G.C1 m cs) ρ where - toNativeGeneral' xs = G.M1 $ toNativeGeneral' xs - -instance ToNativeGeneralG G.U1 ρ where - toNativeGeneral' _ = G.U1 - -instance (KnownSymbol name, ρ .! name ≈ t) - => ToNativeGeneralG (G.S1 ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) ρ where - toNativeGeneral' r = G.M1 $ G.K1 $ r .! (Label @name) - -instance (ToNativeGeneralG l ρ, ToNativeGeneralG r ρ) - => ToNativeGeneralG (l G.:*: r) ρ where - toNativeGeneral' r = toNativeGeneral' r G.:*: toNativeGeneral' r - -type ToNativeGeneral t ρ = (G.Generic t, ToNativeGeneralG (G.Rep t) ρ) - --- | Convert a record to a native Haskell type. -toNativeGeneral :: ToNativeGeneral t ρ => Rec ρ -> t -toNativeGeneral = G.to . toNativeGeneral' - - -{-------------------------------------------------------------------- - Generic-lens compatibility ---------------------------------------------------------------------} - --- | Every field in a row-types based record has a 'HasField' instance. -instance {-# OVERLAPPING #-} - ( KnownSymbol name - , r' .! name ≈ b - , r .! name ≈ a - , r' ~ Modify name b r - , r ~ Modify name a r') - => HasField name (Rec r) (Rec r') a b where - field = focus (Label @name) - {-# INLINE field #-} - -instance {-# OVERLAPPING #-} - ( KnownSymbol name - , r .! name ≈ a - , r ~ Modify name a r) - => HasField' name (Rec r) a where - field' = focus (Label @name) - {-# INLINE field' #-}
− Data/Row/Switch.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE FunctionalDependencies #-} ------------------------------------------------------------------------------ --- | --- Module: Data.Row.Switch --- --- This module provides the ability to discharge a polymorphic variant using --- a record that has matching fields. --- ------------------------------------------------------------------------------ - - -module Data.Row.Switch - ( - Switch(..) - ) -where - -import Data.Row.Internal -import Data.Row.Records -import Data.Row.Variants - - - --- | A 'Var' and a 'Rec' can combine if their rows line up properly. -class Switch (v :: Row *) (r :: Row *) x | v x -> r, r x -> v where - {-# MINIMAL switch | caseon #-} - -- | Given a Variant and a Record of functions from each possible value - -- of the variant to a single output type, apply the correct - -- function to the value in the variant. - switch :: Var v -> Rec r -> x - switch = flip caseon - -- | The same as @switch@ but with the argument order reversed - caseon :: Rec r -> Var v -> x - caseon = flip switch - - -instance Switch (R '[]) (R '[]) x where - switch = const . impossible - -instance (KnownSymbol l, Switch (R v) (R r) b) - => Switch (R (l :-> a ': v)) (R (l :-> (a -> b) ': r)) b where - switch v r = case trial v l of - Left x -> (r .! l) x - Right v -> switch v (unsafeRemove l r) - where l = Label @l
− Data/Row/Variants.hs
@@ -1,565 +0,0 @@------------------------------------------------------------------------------ --- | --- Module : Data.Row.Variants --- --- This module implements extensible variants using closed type families. --- ------------------------------------------------------------------------------ - - -module Data.Row.Variants - ( - -- * Types and constraints - Label(..) - , KnownSymbol, AllUniqueLabels, WellBehaved - , Var, Row, Empty, type (≈) - -- * Construction - , HasType, pattern IsJust, singleton, unSingleton - , fromLabels - -- ** Extension - , type (.\), Lacks, type (.\/), diversify, type (.+) - -- ** Modification - , update, focus, Modify, rename, Rename - -- * Destruction - , impossible, trial, trial', multiTrial, view - , restrict, split - -- ** Types for destruction - , type (.!), type (.-), type (.\\), type (.==) - -- * Native Conversion - -- $native - , toNative, fromNative, fromNativeGeneral - , ToNative, FromNative, FromNativeGeneral - , NativeRow - -- * Row operations - -- ** Map - , Map, map, map', transform, transform' - -- ** Fold - , Forall, erase, eraseWithLabels, eraseZip - -- ** Sequence - , sequence - -- ** Compose - -- $compose - , compose, uncompose - -- ** labels - , labels - -- ** Coerce - , coerceVar - -- ** UNSAFE operations - , unsafeMakeVar, unsafeInjectFront - ) -where - -import Prelude hiding (map, sequence, zip) - -import Control.Applicative -import Control.Arrow ((+++), left, right) -import Control.DeepSeq (NFData(..), deepseq) - -import Data.Coerce -import Data.Functor.Compose -import Data.Functor.Identity -import Data.Functor.Product -import Data.Generics.Sum.Constructors (AsConstructor(..), AsConstructor'(..)) -import Data.Maybe (fromMaybe) -import Data.Profunctor (Choice(..), Profunctor(..)) -import Data.Proxy -import Data.String (IsString) -import Data.Text (Text) - -import qualified GHC.Generics as G -import GHC.TypeLits - -import Unsafe.Coerce - -import Data.Row.Internal - -{-------------------------------------------------------------------- - Polymorphic Variants ---------------------------------------------------------------------} - --- | The variant type. -data Var (r :: Row *) where - OneOf :: Text -> HideType -> Var r - -instance Forall r Show => Show (Var r) where - show v = (\ (x, y) -> "{" ++ x ++ "=" ++ y ++ "}") $ eraseWithLabels @Show show v - -instance Forall r Eq => Eq (Var r) where - r == r' = fromMaybe False $ eraseZip @Eq (==) r r' - -instance (Forall r Eq, Forall r Ord) => Ord (Var r) where - compare :: Var r -> Var r -> Ordering - compare x y = getConst $ metamorph' @_ @r @Ord @(Product Var Var) @(Const Ordering) @(Const Ordering) Proxy doNil doUncons doCons (Pair x y) - where doNil (Pair x _) = impossible x - doUncons l (Pair r1 r2) = case (trial r1 l, trial r2 l) of - (Left a, Left b) -> Left $ Const $ compare a b - (Left _, Right _) -> Left $ Const LT - (Right _, Left _) -> Left $ Const GT - (Right x, Right y) -> Right $ Pair x y - doCons _ (Left (Const c)) = Const c - doCons _ (Right (Const c)) = Const c - -instance Forall r NFData => NFData (Var r) where - rnf r = getConst $ metamorph' @_ @r @NFData @Var @(Const ()) @Identity Proxy empty doUncons doCons r - where empty = const $ Const () - doUncons l = left Identity . flip trial l - doCons _ x = deepseq x $ Const () - - -{-------------------------------------------------------------------- - Basic Operations ---------------------------------------------------------------------} - --- | An unsafe way to make a Variant. This function does not guarantee that --- the labels are all unique. -unsafeMakeVar :: forall r l. KnownSymbol l => Label l -> r .! l -> Var r -unsafeMakeVar (toKey -> l) = OneOf l . HideType - --- | A Variant with no options is uninhabited. -impossible :: Var Empty -> a -impossible _ = error "Impossible! Somehow, a variant of nothing was produced." - --- | A quick constructor to create a singleton variant. -singleton :: KnownSymbol l => Label l -> a -> Var (l .== a) -singleton = IsJust - --- | A quick destructor for singleton variants. -unSingleton :: forall l a. KnownSymbol l => Var (l .== a) -> (Label l, a) -unSingleton (OneOf _ (HideType x)) = (l, unsafeCoerce x) where l = Label @l - --- | A pattern for variants; can be used to both destruct a variant --- when in a pattern position or construct one in an expression position. -pattern IsJust :: forall l r. (AllUniqueLabels r, KnownSymbol l) => Label l -> r .! l -> Var r -pattern IsJust l a <- (isJustHelper @l -> (l, Just a)) where - IsJust l a = unsafeMakeVar l a - -isJustHelper :: forall l r. KnownSymbol l => Var r -> (Label l, Maybe (r .! l)) -isJustHelper v = (l, view l v) where l = Label @l - --- | Make the variant arbitrarily more diverse. -diversify :: forall r' r. Var r -> Var (r .\/ r') -diversify = unsafeCoerce -- (OneOf l x) = OneOf l x - --- | If the variant exists at the given label, update it to the given value. --- Otherwise, do nothing. -update :: (KnownSymbol l, r .! l ≈ a) => Label l -> a -> Var r -> Var r -update (toKey -> l') a (OneOf l x) = OneOf l $ if l == l' then HideType a else x - --- | If the variant exists at the given label, focus on the value associated with it. --- Otherwise, do nothing. -focus :: forall l r r' a b p f. - ( AllUniqueLabels r - , AllUniqueLabels r' - , KnownSymbol l - , r .! l ≈ a - , r' .! l ≈ b - , r' ≈ (r .- l) .\/ (l .== b) - , Applicative f - , Choice p - ) => Label l -> p a (f b) -> p (Var r) (f (Var r')) -focus (toKey -> l) = - dimap unwrap rewrap . left' - where - unwrap :: Var r -> Either a (Var r') - unwrap (OneOf l' (HideType x)) - | l == l' = Left (unsafeCoerce x) - | otherwise = Right (OneOf l' (HideType x)) - rewrap :: Either (f b) (Var r') -> f (Var r') - rewrap = either (fmap $ OneOf l . HideType) pure - --- | Rename the given label. -rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Var r -> Var (Rename l l' r) -rename (toKey -> l1) (toKey -> l2) (OneOf l x) = OneOf (if l == l1 then l2 else l) x - --- | Convert a variant into either the value at the given label or a variant without --- that label. This is the basic variant destructor. -trial :: KnownSymbol l => Var r -> Label l -> Either (r .! l) (Var (r .- l)) -trial (OneOf l (HideType x)) (toKey -> l') = if l == l' then Left (unsafeCoerce x) else Right (OneOf l (HideType x)) - --- | A version of 'trial' that ignores the leftover variant. -trial' :: KnownSymbol l => Var r -> Label l -> Maybe (r .! l) -trial' = (either Just (const Nothing) .) . trial - --- | A trial over multiple types -multiTrial :: forall x y. (AllUniqueLabels x, Forall (y .\\ x) Unconstrained1) => Var y -> Either (Var x) (Var (y .\\ x)) -multiTrial (OneOf l x) = if l `elem` labels @(y .\\ x) @Unconstrained1 then Right (OneOf l x) else Left (OneOf l x) - --- | A convenient function for using view patterns when dispatching variants. --- For example: --- --- @ --- myShow :: Var ("y" '::= String :| "x" '::= Int :| Empty) -> String --- myShow (view x -> Just n) = "Int of "++show n --- myShow (view y -> Just s) = "String of "++s @ -view :: KnownSymbol l => Label l -> Var r -> Maybe (r .! l) -view = flip trial' - --- | Split a variant into two sub-variants. -split :: forall s r. (WellBehaved s, Subset s r) => Var r -> Either (Var s) (Var (r .\\ s)) -split (OneOf l a) | l `elem` labels @s @Unconstrained1 = Left $ OneOf l a - | otherwise = Right $ OneOf l a - --- | Arbitrary variant restriction. Turn a variant into a subset of itself. -restrict :: forall r r'. (WellBehaved r, Subset r r') => Var r' -> Maybe (Var r) -restrict = either Just (pure Nothing) . split - - -{-------------------------------------------------------------------- - Folds and maps ---------------------------------------------------------------------} - --- | A standard fold -erase :: forall c ρ b. Forall ρ c => (forall a. c a => a -> b) -> Var ρ -> b -erase f = snd @String . eraseWithLabels @c f - --- | A fold with labels -eraseWithLabels :: forall c ρ s b. (Forall ρ c, IsString s) => (forall a. c a => a -> b) -> Var ρ -> (s,b) -eraseWithLabels f = getConst . metamorph' @_ @ρ @c @Var @(Const (s,b)) @Identity Proxy impossible doUncons doCons - where doUncons l = left Identity . flip trial l - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Either (Identity τ) (Const (s,b) ('R ρ)) -> Const (s,b) ('R (ℓ :-> τ ': ρ)) - doCons l (Left (Identity x)) = Const (show' l, f x) - doCons _ (Right (Const c)) = Const c - --- | A fold over two row type structures at once -eraseZip :: forall c ρ b. Forall ρ c => (forall a. c a => a -> a -> b) -> Var ρ -> Var ρ -> Maybe b -eraseZip f x y = getConst $ metamorph' @_ @ρ @c @(Product Var Var) @(Const (Maybe b)) @(Const (Maybe b)) Proxy doNil doUncons doCons (Pair x y) - where doNil _ = Const Nothing - doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Product Var Var ('R (ℓ :-> τ ': ρ)) -> Either (Const (Maybe b) τ) (Product Var Var ('R ρ)) - doUncons l (Pair r1 r2) = case (trial r1 l, trial r2 l) of - (Left a, Left b) -> Left $ Const $ Just $ f a b - (Right x, Right y) -> Right $ Pair x y - _ -> Left $ Const Nothing - doCons _ (Left (Const c)) = Const c - doCons _ (Right (Const c)) = Const c - - --- | VMap is used internally as a type level lambda for defining variant maps. -newtype VMap (f :: * -> *) (ρ :: Row *) = VMap { unVMap :: Var (Map f ρ) } -newtype VMap2 (f :: * -> *) (g :: * -> *) (ρ :: Row *) = VMap2 { unVMap2 :: Var (Map f (Map g ρ)) } - --- | A function to map over a variant given a constraint. -map :: forall c f r. Forall r c => (forall a. c a => a -> f a) -> Var r -> Var (Map f r) -map f = unVMap . metamorph' @_ @r @c @Var @(VMap f) @Identity Proxy doNil doUncons doCons - where - doNil = impossible - doUncons l = left Identity . flip trial l - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Either (Identity τ) (VMap f ('R ρ)) -> VMap f ('R (ℓ :-> τ ': ρ)) - doCons l (Left (Identity x)) = VMap $ unsafeMakeVar l $ f x - doCons _ (Right (VMap v)) = VMap $ unsafeInjectFront v - --- | A function to map over a variant given no constraint. -map' :: forall f r. Forall r Unconstrained1 => (forall a. a -> f a) -> Var r -> Var (Map f r) -map' = map @Unconstrained1 - --- | Lifts a natrual transformation over a variant. In other words, it acts as a --- variant transformer to convert a variant of @f a@ values to a variant of @g a@ --- values. If no constraint is needed, instantiate the first type argument with --- 'Unconstrained1'. -transform :: forall r c (f :: * -> *) (g :: * -> *). Forall r c => (forall a. c a => f a -> g a) -> Var (Map f r) -> Var (Map g r) -transform f = unVMap . metamorph' @_ @r @c @(VMap f) @(VMap g) @f Proxy doNil doUncons doCons . VMap - where - doNil = impossible . unVMap - doUncons l = right VMap . flip trial l . unVMap - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Either (f τ) (VMap g ('R ρ)) -> VMap g ('R (ℓ :-> τ ': ρ)) - doCons l (Left x) = VMap $ unsafeMakeVar l $ f x - doCons _ (Right (VMap v)) = VMap $ unsafeInjectFront v - --- | A form of @transformC@ that doesn't have a constraint on @a@ -transform' :: forall r (f :: * -> *) (g :: * -> *) . Forall r Unconstrained1 => (forall a. f a -> g a) -> Var (Map f r) -> Var (Map g r) -transform' = transform @r @Unconstrained1 - --- | Applicative sequencing over a variant -sequence :: forall f r. (Forall r Unconstrained1, Applicative f) => Var (Map f r) -> f (Var r) -sequence = getCompose . metamorph' @_ @r @Unconstrained1 @(VMap f) @(Compose f Var) @f Proxy doNil doUncons doCons . VMap - where - doNil = impossible . unVMap - doUncons l = right VMap . flip trial l . unVMap - doCons l (Left fx) = Compose $ unsafeMakeVar l <$> fx - doCons _ (Right (Compose v)) = Compose $ unsafeInjectFront <$> v - --- $compose --- We can easily convert between mapping two functors over the types of a row --- and mapping the composition of the two functors. The following two functions --- perform this composition with the gaurantee that: --- --- >>> compose . uncompose = id --- --- >>> uncompose . compose = id - --- | Convert from a variant where two functors have been mapped over the types to --- one where the composition of the two functors is mapped over the types. -compose :: forall (f :: * -> *) (g :: * -> *) r . Forall r Unconstrained1 => Var (Map f (Map g r)) -> Var (Map (Compose f g) r) -compose = unVMap . metamorph' @_ @r @Unconstrained1 @(VMap2 f g) @(VMap (Compose f g)) Proxy doNil doUncons doCons . VMap2 - where - doNil = impossible . unVMap2 - doUncons l = (Compose +++ VMap2) . flip trial l . unVMap2 - doCons l (Left x) = VMap $ unsafeMakeVar l x - doCons _ (Right (VMap v)) = VMap $ unsafeInjectFront v - --- | Convert from a variant where the composition of two functors have been mapped --- over the types to one where the two functors are mapped individually one at a --- time over the types. -uncompose :: forall (f :: * -> *) (g :: * -> *) r . Forall r Unconstrained1 => Var (Map (Compose f g) r) -> Var (Map f (Map g r)) -uncompose = unVMap2 . metamorph' @_ @r @Unconstrained1 @(VMap (Compose f g)) @(VMap2 f g) Proxy doNil doUncons doCons . VMap - where - doNil = impossible . unVMap - doUncons l = right VMap . flip trial l . unVMap - doCons l (Left (Compose x)) = VMap2 $ unsafeMakeVar l x - doCons _ (Right (VMap2 v)) = VMap2 $ unsafeInjectFront v - - --- | Coerce a variant to a coercible representation. The 'BiForall' in the context --- indicates that the type of any option in @r1@ can be coerced to the type of --- the corresponding option in @r2@. --- --- Internally, this is implemented just with `unsafeCoerce`, but we provide the --- following implementation as a proof: --- --- > newtype ConstR a b = ConstR { unConstR :: Var a } --- > newtype FlipConstR a b = FlipConstR { unFlipConstR :: Var b } --- > coerceVar = unFlipConstR . biMetamorph' @_ @_ @r1 @r2 @Coercible @ConstR @FlipConstR @Const Proxy doNil doUncons doCons . ConstR --- > where --- > doNil = impossible . unConstR --- > doUncons l = (Const +++ ConstR) . flip trial l . unConstR --- > doCons :: forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, Coercible τ1 τ2) --- > => Label ℓ -> Either (Const τ1 τ2) (FlipConstR ('R ρ1) ('R ρ2)) --- > -> FlipConstR ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2)) --- > doCons l (Left (Const x)) = FlipConstR $ unsafeMakeVar l (coerce @τ1 @τ2 x) --- > doCons _ (Right (FlipConstR v)) = FlipConstR $ unsafeInjectFront v -coerceVar :: forall r1 r2. BiForall r1 r2 Coercible => Var r1 -> Var r2 -coerceVar = unsafeCoerce - -{-------------------------------------------------------------------- - Variant initialization ---------------------------------------------------------------------} - --- | A helper function for unsafely adding an element to the front of a variant. --- This can cause the type of the resulting variant to be malformed, for instance, --- if the variant already contains labels that are lexicographically before the --- given label. Realistically, this function should only be used when writing --- calls to 'metamorph'. -unsafeInjectFront :: forall l a r. KnownSymbol l => Var (R r) -> Var (R (l :-> a ': r)) -unsafeInjectFront = unsafeCoerce - --- | Initialize a variant from a producer function that accepts labels. If this --- function returns more than one possibility, then one is chosen arbitrarily to --- be the value in the variant. -fromLabels :: forall c ρ f. (Alternative f, Forall ρ c, AllUniqueLabels ρ) - => (forall l a. (KnownSymbol l, c a) => Label l -> f a) -> f (Var ρ) -fromLabels mk = getCompose $ metamorph' @_ @ρ @c @(Const ()) @(Compose f Var) @(Const ()) - Proxy doNil doUncons doCons (Const ()) - where doNil _ = Compose $ empty - doUncons _ _ = Right $ Const () - doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) - => Label ℓ -> Either (Const () τ) (Compose f Var ('R ρ)) -> Compose f Var ('R (ℓ :-> τ ': ρ)) - doCons l (Left _) = Compose $ unsafeMakeVar l <$> mk l --This case should be impossible - doCons l (Right (Compose v)) = Compose $ - unsafeMakeVar l <$> mk l <|> unsafeInjectFront <$> v - -{-------------------------------------------------------------------- - Generic instance ---------------------------------------------------------------------} - --- The generic structure we want Vars to have is not the hidden internal one, --- but rather one that appears as a Haskell sum type. Thus, we can't derive --- Generic automatically. --- --- The following Generic instance creates a representation of a Var that is --- very similar to a native Haskell sum type except that the tree of possibilities (':+:') --- that it produces will be extremely unbalanced. I don't think this is a problem. --- Furthermore, because we don't want Vars to always have a trailing void option on --- the end, we must have a special case for singleton Vars, which means that --- we can't use metamorph. - -instance GenericVar r => G.Generic (Var r) where - type Rep (Var r) = - G.D1 ('G.MetaData "Var" "Data.Row.Variants" "row-types" 'False) (RepVar r) - from = G.M1 . fromVar - to = toVar . G.unM1 - -class GenericVar r where - type RepVar (r :: Row *) :: * -> * - fromVar :: Var r -> RepVar r x - toVar :: RepVar r x -> Var r - -instance GenericVar Empty where - type RepVar Empty = G.V1 - fromVar = impossible - toVar = \case - -instance KnownSymbol name => GenericVar (R '[name :-> t]) where - type RepVar (R (name :-> t ': '[])) = G.C1 - ('G.MetaCons name 'G.PrefixI 'False) - (G.S1 ('G.MetaSel 'Nothing 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) - (G.Rec0 t)) - fromVar (unSingleton -> (_, a)) = G.M1 (G.M1 (G.K1 a)) - toVar (G.M1 (G.M1 (G.K1 a))) = IsJust (Label @name) a - -instance - ( GenericVar (R (name' :-> t' ': r')) - , KnownSymbol name - , AllUniqueLabels (R (name :-> t ': (name' :-> t' ': r'))) - ) => GenericVar (R (name :-> t ': (name' :-> t' ': r'))) where - type RepVar (R (name :-> t ': (name' :-> t' ': r'))) = (G.C1 - ('G.MetaCons name 'G.PrefixI 'False) - (G.S1 ('G.MetaSel 'Nothing 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) - (G.Rec0 t))) G.:+: RepVar (R (name' :-> t' ': r')) - fromVar v = case trial @name v Label of - Left a -> G.L1 (G.M1 (G.M1 (G.K1 a))) - Right v' -> G.R1 (fromVar v') - toVar (G.L1 (G.M1 (G.M1 (G.K1 a)))) = IsJust (Label @name) a - toVar (G.R1 g) = unsafeInjectFront $ toVar g - -{-------------------------------------------------------------------- - Native data type compatibility ---------------------------------------------------------------------} - --- $native --- The 'toNative' and 'fromNative' functions allow one to convert between --- 'Var's and regular Haskell data types ("native" types) that have the same --- number of constructors such that each constructor has one field and the same --- name as one of the options of the 'Var', which has the same type as that field. --- As expected, they compose to form the identity. Alternatively, one may use --- 'fromNativeGeneral', which allows a variant with excess options to still be --- transformed to a native type. Because of this, 'fromNativeGeneral' requires a type --- application (although 'fromNative' does not). The only requirement is that --- the native Haskell data type be an instance of 'Generic'. --- --- For example, consider the following simple data type: --- --- >>> data Pet = Dog {age :: Int} | Cat {age :: Int} deriving (Generic, Show) --- --- Then, we have the following: --- --- >>> toNative $ IsJust (Label @"Dog") 3 :: Pet --- Dog {age = 3} --- >>> V.fromNative $ Dog 3 :: Var ("Dog" .== Int .+ "Cat" .== Int) --- {Dog=3} - -type family NativeRow t where - NativeRow t = NativeRowG (G.Rep t) - -type family NativeRowG t where - NativeRowG (G.M1 G.D m cs) = NativeRowG cs - NativeRowG G.V1 = Empty - NativeRowG (l G.:+: r) = NativeRowG l .+ NativeRowG r - NativeRowG (G.C1 ('G.MetaCons name fixity sels) (G.S1 m (G.Rec0 t))) = name .== t - - --- | Conversion helper to bring a variant back into a Haskell type. Note that the --- native Haskell type must be an instance of 'Generic'. -class ToNativeG a where - toNative' :: Var (NativeRowG a) -> a x - -instance ToNativeG cs => ToNativeG (G.D1 m cs) where - toNative' = G.M1 . toNative' - -instance ToNativeG G.V1 where - toNative' = impossible - -instance (KnownSymbol name) - => ToNativeG (G.C1 ('G.MetaCons name fixity sels) - (G.S1 m (G.Rec0 t))) where - toNative' = G.M1 . G.M1 . G.K1 . snd . unSingleton - -instance ( ToNativeG l, ToNativeG r, (NativeRowG l .+ NativeRowG r) .\\ NativeRowG l ≈ NativeRowG r - , AllUniqueLabels (NativeRowG l), Forall (NativeRowG r) Unconstrained1) - => ToNativeG (l G.:+: r) where - toNative' v = case multiTrial @(NativeRowG l) @(NativeRowG (l G.:+: r)) v of - Left v' -> G.L1 $ toNative' v' - Right v' -> G.R1 $ toNative' v' - -type ToNative t = (G.Generic t, ToNativeG (G.Rep t)) - --- | Convert a variant to a native Haskell type. -toNative :: ToNative t => Var (NativeRow t) -> t -toNative = G.to . toNative' - - --- | Conversion helper to turn a Haskell variant into a row-types extensible --- variant. Note that the native Haskell type must be an instance of 'Generic'. -class FromNativeG a where - fromNative' :: a x -> Var (NativeRowG a) - -instance FromNativeG cs => FromNativeG (G.D1 m cs) where - fromNative' (G.M1 v) = fromNative' v - -instance FromNativeG G.V1 where - fromNative' = \ case - -instance KnownSymbol name - => FromNativeG (G.C1 ('G.MetaCons name fixity sels) - (G.S1 m (G.Rec0 t))) where - fromNative' (G.M1 (G.M1 (G.K1 x))) = IsJust (Label @name) x - -instance (FromNativeG l, FromNativeG r) => FromNativeG (l G.:+: r) where - -- Ideally, we would use 'diversify' here instead of 'unsafeCoerce', but it - -- makes the constraints really hairy. - fromNative' (G.L1 x) = unsafeCoerce $ fromNative' @l x - fromNative' (G.R1 y) = unsafeCoerce $ fromNative' @r y - -type FromNative t = (G.Generic t, FromNativeG (G.Rep t)) - --- | Convert a Haskell record to a row-types Var. -fromNative :: FromNative t => t -> Var (NativeRow t) -fromNative = fromNative' . G.from - - --- | Conversion helper to turn a Haskell variant into a row-types extensible --- variant. Note that the native Haskell type must be an instance of 'Generic'. -class FromNativeGeneralG a ρ where - fromNativeGeneral' :: a x -> Var ρ - -instance FromNativeGeneralG cs ρ => FromNativeGeneralG (G.D1 m cs) ρ where - fromNativeGeneral' (G.M1 v) = fromNativeGeneral' v - -instance FromNativeGeneralG G.V1 ρ where - fromNativeGeneral' = \ case - -instance (KnownSymbol name, ρ .! name ≈ t, AllUniqueLabels ρ) - => FromNativeGeneralG (G.C1 ('G.MetaCons name fixity sels) - (G.S1 m (G.Rec0 t))) ρ where - fromNativeGeneral' (G.M1 (G.M1 (G.K1 x))) = IsJust (Label @name) x - -instance (FromNativeGeneralG l ρ, FromNativeGeneralG r ρ) - => FromNativeGeneralG (l G.:+: r) ρ where - -- Ideally, we would use 'diversify' here instead of 'unsafeCoerce', but it - -- makes the constraints really hairy. - fromNativeGeneral' (G.L1 x) = unsafeCoerce $ fromNativeGeneral' @l @ρ x - fromNativeGeneral' (G.R1 y) = unsafeCoerce $ fromNativeGeneral' @r @ρ y - -type FromNativeGeneral t ρ = (G.Generic t, FromNativeGeneralG (G.Rep t) ρ) - --- | Convert a Haskell record to a row-types Var. -fromNativeGeneral :: FromNativeGeneral t ρ => t -> Var ρ -fromNativeGeneral = fromNativeGeneral' . G.from - - -{-------------------------------------------------------------------- - Generic-lens compatibility ---------------------------------------------------------------------} - --- | Every possibility of a row-types based variant has an 'AsConstructor' instance. -instance {-# OVERLAPPING #-} - ( AllUniqueLabels r - , AllUniqueLabels r' - , KnownSymbol name - , r .! name ≈ a - , r' .! name ≈ b - , r' ≈ (r .- name) .\/ (name .== b)) - => AsConstructor name (Var r) (Var r') a b where - _Ctor = focus (Label @name) - {-# INLINE _Ctor #-} - -instance {-# OVERLAPPING #-} - ( AllUniqueLabels r - , KnownSymbol name - , r .! name ≈ a - , r ≈ (r .- name) .\/ (name .== a)) - => AsConstructor' name (Var r) a where - _Ctor' = focus (Label @name) - {-# INLINE _Ctor' #-}
benchmarks/perf/Main.hs view
@@ -1,6 +1,6 @@ module Main (main) where -import Criterion.Main +import Gauge.Main import Data.String @@ -20,42 +20,42 @@ .+ "i20" .== a .+ "i21" .== a .+ "i22" .== a .+ "i23" .== a .+ "i30" .== a .+ "i31" .== a .+ "i32" .== a .+ "i33" .== a -type SixtyFourRecord a = - "i0" .== a .+ "i1" .== a .+ "i2" .== a .+ "i3" .== a - .+ "i10" .== a .+ "i11" .== a .+ "i12" .== a .+ "i13" .== a - .+ "i20" .== a .+ "i21" .== a .+ "i22" .== a .+ "i23" .== a - .+ "i30" .== a .+ "i31" .== a .+ "i32" .== a .+ "i33" .== a - .+ "i100" .== a .+ "i101" .== a .+ "i102" .== a .+ "i103" .== a - .+ "i110" .== a .+ "i111" .== a .+ "i112" .== a .+ "i113" .== a - .+ "i120" .== a .+ "i121" .== a .+ "i122" .== a .+ "i123" .== a - .+ "i130" .== a .+ "i131" .== a .+ "i132" .== a .+ "i133" .== a - .+ "i200" .== a .+ "i201" .== a .+ "i202" .== a .+ "i203" .== a - .+ "i210" .== a .+ "i211" .== a .+ "i212" .== a .+ "i213" .== a - .+ "i220" .== a .+ "i221" .== a .+ "i222" .== a .+ "i223" .== a - .+ "i230" .== a .+ "i231" .== a .+ "i232" .== a .+ "i233" .== a - .+ "i300" .== a .+ "i301" .== a .+ "i302" .== a .+ "i303" .== a - .+ "i310" .== a .+ "i311" .== a .+ "i312" .== a .+ "i313" .== a - .+ "i320" .== a .+ "i321" .== a .+ "i322" .== a .+ "i323" .== a - .+ "i330" .== a .+ "i331" .== a .+ "i332" .== a .+ "i333" .== a +-- type SixtyFourRecord a = +-- "i0" .== a .+ "i1" .== a .+ "i2" .== a .+ "i3" .== a +-- .+ "i10" .== a .+ "i11" .== a .+ "i12" .== a .+ "i13" .== a +-- .+ "i20" .== a .+ "i21" .== a .+ "i22" .== a .+ "i23" .== a +-- .+ "i30" .== a .+ "i31" .== a .+ "i32" .== a .+ "i33" .== a +-- .+ "i100" .== a .+ "i101" .== a .+ "i102" .== a .+ "i103" .== a +-- .+ "i110" .== a .+ "i111" .== a .+ "i112" .== a .+ "i113" .== a +-- .+ "i120" .== a .+ "i121" .== a .+ "i122" .== a .+ "i123" .== a +-- .+ "i130" .== a .+ "i131" .== a .+ "i132" .== a .+ "i133" .== a +-- .+ "i200" .== a .+ "i201" .== a .+ "i202" .== a .+ "i203" .== a +-- .+ "i210" .== a .+ "i211" .== a .+ "i212" .== a .+ "i213" .== a +-- .+ "i220" .== a .+ "i221" .== a .+ "i222" .== a .+ "i223" .== a +-- .+ "i230" .== a .+ "i231" .== a .+ "i232" .== a .+ "i233" .== a +-- .+ "i300" .== a .+ "i301" .== a .+ "i302" .== a .+ "i303" .== a +-- .+ "i310" .== a .+ "i311" .== a .+ "i312" .== a .+ "i313" .== a +-- .+ "i320" .== a .+ "i321" .== a .+ "i322" .== a .+ "i323" .== a +-- .+ "i330" .== a .+ "i331" .== a .+ "i332" .== a .+ "i333" .== a -my64Record :: Rec (SixtyFourRecord Double) -my64Record = - #i0 .== 0 .+ #i1 .== 0 .+ #i2 .== 0 .+ #i3 .== 0 - .+ #i10 .== 0 .+ #i11 .== 0 .+ #i12 .== 0 .+ #i13 .== 0 - .+ #i20 .== 0 .+ #i21 .== 0 .+ #i22 .== 0 .+ #i23 .== 0 - .+ #i30 .== 0 .+ #i31 .== 0 .+ #i32 .== 0 .+ #i33 .== 0 - .+ #i100 .== 0 .+ #i101 .== 0 .+ #i102 .== 0 .+ #i103 .== 0 - .+ #i110 .== 0 .+ #i111 .== 0 .+ #i112 .== 0 .+ #i113 .== 0 - .+ #i120 .== 0 .+ #i121 .== 0 .+ #i122 .== 0 .+ #i123 .== 0 - .+ #i130 .== 0 .+ #i131 .== 0 .+ #i132 .== 0 .+ #i133 .== 0 - .+ #i200 .== 0 .+ #i201 .== 0 .+ #i202 .== 0 .+ #i203 .== 0 - .+ #i210 .== 0 .+ #i211 .== 0 .+ #i212 .== 0 .+ #i213 .== 0 - .+ #i220 .== 0 .+ #i221 .== 0 .+ #i222 .== 0 .+ #i223 .== 0 - .+ #i230 .== 0 .+ #i231 .== 0 .+ #i232 .== 0 .+ #i233 .== 0 - .+ #i300 .== 0 .+ #i301 .== 0 .+ #i302 .== 0 .+ #i303 .== 0 - .+ #i310 .== 0 .+ #i311 .== 0 .+ #i312 .== 0 .+ #i313 .== 0 - .+ #i320 .== 0 .+ #i321 .== 0 .+ #i322 .== 0 .+ #i323 .== 0 - .+ #i330 .== 0 .+ #i331 .== 0 .+ #i332 .== 0 .+ #i333 .== 0 +-- my64Record :: Rec (SixtyFourRecord Double) +-- my64Record = +-- #i0 .== 0 .+ #i1 .== 0 .+ #i2 .== 0 .+ #i3 .== 0 +-- .+ #i10 .== 0 .+ #i11 .== 0 .+ #i12 .== 0 .+ #i13 .== 0 +-- .+ #i20 .== 0 .+ #i21 .== 0 .+ #i22 .== 0 .+ #i23 .== 0 +-- .+ #i30 .== 0 .+ #i31 .== 0 .+ #i32 .== 0 .+ #i33 .== 0 +-- .+ #i100 .== 0 .+ #i101 .== 0 .+ #i102 .== 0 .+ #i103 .== 0 +-- .+ #i110 .== 0 .+ #i111 .== 0 .+ #i112 .== 0 .+ #i113 .== 0 +-- .+ #i120 .== 0 .+ #i121 .== 0 .+ #i122 .== 0 .+ #i123 .== 0 +-- .+ #i130 .== 0 .+ #i131 .== 0 .+ #i132 .== 0 .+ #i133 .== 0 +-- .+ #i200 .== 0 .+ #i201 .== 0 .+ #i202 .== 0 .+ #i203 .== 0 +-- .+ #i210 .== 0 .+ #i211 .== 0 .+ #i212 .== 0 .+ #i213 .== 0 +-- .+ #i220 .== 0 .+ #i221 .== 0 .+ #i222 .== 0 .+ #i223 .== 0 +-- .+ #i230 .== 0 .+ #i231 .== 0 .+ #i232 .== 0 .+ #i233 .== 0 +-- .+ #i300 .== 0 .+ #i301 .== 0 .+ #i302 .== 0 .+ #i303 .== 0 +-- .+ #i310 .== 0 .+ #i311 .== 0 .+ #i312 .== 0 .+ #i313 .== 0 +-- .+ #i320 .== 0 .+ #i321 .== 0 .+ #i322 .== 0 .+ #i323 .== 0 +-- .+ #i330 .== 0 .+ #i331 .== 0 .+ #i332 .== 0 .+ #i333 .== 0 main :: IO () main = @@ -70,9 +70,9 @@ , bench "recordFromLabels 11" $ nf id $ fromLabels @IsString @(ElevenRecord String) (fromString . show) , bench "default 16" $ nf id $ default' @Num @(SixteenRecord Double) 0 , bench "recordFromLabels 16" $ nf id $ fromLabels @IsString @(SixteenRecord String) (fromString . show) - , bench "simple 64" $ nf id $ my64Record - , bench "default 64" $ nf id $ default' @Num @(SixtyFourRecord Double) 0 - , bench "recordFromLabels 64" $ nf id $ fromLabels @IsString @(SixtyFourRecord String) (fromString . show) + -- , bench "simple 64" $ nf id $ my64Record + -- , bench "default 64" $ nf id $ default' @Num @(SixtyFourRecord Double) 0 + -- , bench "recordFromLabels 64" $ nf id $ fromLabels @IsString @(SixtyFourRecord String) (fromString . show) ] , bgroup "Record Append" [ bench "append 3 3" $ nf (uncurry (.+)) (#a .== () .+ #b .== () .+ #c .== (), #d .== () .+ #e .== () .+ #f .== ()) @@ -81,15 +81,15 @@ ] , bgroup "Record Access" [ bench "get 2 of 4" $ nf (.! #i1) $ default' @Num @(FourRecord Double) 0 - [ bench "get 7 of 11" $ nf (.! #i1) $ default' @Num @(ElevenRecord Double) 0 + , bench "get 7 of 11" $ nf (.! #i1) $ default' @Num @(ElevenRecord Double) 0 , bench "get 4 of 16" $ nf (.! #i10) $ default' @Num @(SixteenRecord Double) 0 , bench "get 16 of 16" $ nf (.! #i33) $ default' @Num @(SixteenRecord Double) 0 - , bench "get 4 of 64" $ nf (.! #i10) $ default' @Num @(SixtyFourRecord Double) 1 - , bench "get 45 of 64" $ nf (.! #i230) $ default' @Num @(SixtyFourRecord Double) 2 - , bench "get 63 of 64" $ nf (.! #i332) $ default' @Num @(SixtyFourRecord Double) 3 + -- , bench "get 4 of 64" $ nf (.! #i10) $ default' @Num @(SixtyFourRecord Double) 1 + -- , bench "get 45 of 64" $ nf (.! #i230) $ default' @Num @(SixtyFourRecord Double) 2 + -- , bench "get 63 of 64" $ nf (.! #i332) $ default' @Num @(SixtyFourRecord Double) 3 ] , bgroup "Record Metamorphosis" [ bench "erase 4" $ nf (erase @Show show) $ #a .== () .+ #b .== () .+ #c .== () .+ #d .== () - , bench "erase 64" $ nf (erase @Show show) $ my64Record + -- , bench "erase 64" $ nf (erase @Show show) $ my64Record ] ]
examples/Examples.lhs view
@@ -1,6 +1,12 @@-> {-# LANGUAGE OverloadedLabels #-} +> {-# LANGUAGE AllowAmbiguousTypes #-} +> {-# LANGUAGE DataKinds #-} > {-# LANGUAGE DeriveGeneric #-} +> {-# LANGUAGE FlexibleContexts #-} +> {-# LANGUAGE OverloadedLabels #-} > {-# LANGUAGE PartialTypeSignatures #-} +> {-# LANGUAGE ScopedTypeVariables #-} +> {-# LANGUAGE TypeOperators #-} +> {-# LANGUAGE ViewPatterns #-} > module Examples where > > import Data.Row @@ -153,8 +159,7 @@ λ> distance (move (named "2D" origin3D) 5 12) 13.0 -Note that if we were using row-types-lens and the lens library, we could write -move as: +Or, with lenses, we could write move as: > moveLensy p dx dy = p & #x %~ (+ dx) & #y %~ (+ dy) @@ -304,16 +309,16 @@ out, and to do that, you must trial it: λ> trial v #x -Left 1 +Right 1 λ> trial v #y -Right {x=1} +Left {x=1} λ> trial v' #x -Right {y="Foo"} +Left {y="Foo"} λ> trial v' #y -Left "Foo" +Right "Foo" -If trialing at a label l succeeds, then it provides a Left value of the value at l. -If not, it provides a Right value of the variant with this label removed---since the +If trialing at a label l succeeds, then it provides a Right value of the value at l. +If not, it provides a Left value of the variant with this label removed---since the trial failed, we now can be sure that the value is not from l. -------------------------------------------------------------------------------- @@ -409,18 +414,18 @@ λ> :t multiTrial @("x" .== Double .+ "y" .== String) v multiTrial @("x" .== Double .+ "y" .== String) v :: Either - (Var ('R '["x" ':-> Double, "y" ':-> String])) (Var ('R '["x" ':-> Integer])) + (Var ('R '["x" ':-> Double, "y" ':-> String])) λ> multiTrial @("x" .== Double .+ "y" .== String) v -Right {x=1} +Left {x=1} λ> :t multiTrial @("x" .== Double .+ "y" .== String) v' multiTrial @("x" .== Double .+ "y" .== String) v' :: Either - (Var ('R '["x" ':-> Double, "y" ':-> String])) (Var ('R '["x" ':-> Integer])) + (Var ('R '["x" ':-> Double, "y" ':-> String])) λ> multiTrial @("x" .== Double .+ "y" .== String) v' -Left {y="Foo"} +Right {y="Foo"} Thus, multiTrial can be used not only to arbitrarily split apart a variant, but also to change unused label associations (in this case, we changed the variant
row-types.cabal view
@@ -1,5 +1,5 @@ Name: row-types -Version: 0.4.0.0 +Version: 1.0.0.0 License: MIT License-file: LICENSE Author: Daniel Winograd-Cort, Matthew Farkas-Dyck @@ -7,7 +7,7 @@ homepage: https://github.com/target/row-types Build-Type: Simple Cabal-Version: >=1.10 -Tested-With: GHC == 8.4, GHC == 8.6, GHC == 8.8 +Tested-With: GHC == 8.6.5, GHC == 8.8.3, GHC == 8.10.1 Category: Data, Data Structures Synopsis: Open Records and Variants Description: @@ -21,51 +21,41 @@ extra-source-files: examples/Examples.lhs + src/aeson/Data/Row/Aeson.hs + src/barbies/Data/Row/Barbies.hs README.md CHANGELOG.md LICENSE NOTICE Library - Build-Depends: base >= 2 && < 5, - constraints, - deepseq >= 1.4, - hashable >= 1.2, - unordered-containers >= 0.2, - generic-lens >= 1.0.0.0, - profunctors >= 5.0, - text - Exposed-modules: Data.Row - , Data.Row.Internal - , Data.Row.Records - , Data.Row.Variants - , Data.Row.Switch + Build-Depends: + base >= 2 && < 5, + constraints >= 0.11, + deepseq >= 1.4, + hashable >= 1.2, + unordered-containers >= 0.2, + generic-lens >= 1.0.0.0, + profunctors >= 5.0, + text + Exposed-modules: + Data.Row + , Data.Row.Internal + , Data.Row.Dictionaries + , Data.Row.Records + , Data.Row.Variants + , Data.Row.Switch + hs-source-dirs: + src ghc-options: -W default-language: Haskell2010 - default-extensions: AllowAmbiguousTypes, - ConstraintKinds, - DataKinds, - EmptyCase, - EmptyDataDecls, - FlexibleContexts, - FlexibleInstances, - GADTs, - InstanceSigs, - KindSignatures, - LambdaCase, - MultiParamTypeClasses, - OverloadedLabels, - PatternGuards, - PatternSynonyms, - PolyKinds, - RankNTypes, - ScopedTypeVariables, - TypeApplications, - TypeFamilies, - TypeOperators, - TupleSections, - ViewPatterns, - UndecidableInstances + default-extensions: + DataKinds + , ExplicitForAll + , GADTs + , OverloadedLabels + , TypeApplications + , TypeOperators benchmark perf type: exitcode-stdio-1.0 @@ -76,7 +66,7 @@ build-depends: base >= 2 && < 6 , row-types , deepseq >= 1.4 - , criterion >= 1.1 + , gauge >= 0.2.0 default-language: Haskell2010 default-extensions: AllowAmbiguousTypes, DataKinds,
+ src/Data/Row.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE TypeOperators #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row +-- +-- This module includes a set of common functions for Records and Variants. +-- It includes: +-- +-- * Common constructors, destructors, and querying functions +-- +-- It specifically excludes: +-- +-- * Functions that have the same name for Records and Variants (e.g. 'focus', +-- 'update', 'fromLabels', etc.) +-- +-- * Common clashes with the standard Prelude or other modules (e.g. 'map', +-- 'sequence', 'zip', 'Map', etc.) +-- +-- If these particular functions are needed, they should be brought in qualified +-- from one of the Data.Row.*** modules directly. +-- +----------------------------------------------------------------------------- + + +module Data.Row + ( + -- * Types and constraints + Label(..) + , KnownSymbol, AllUniqueLabels, WellBehaved + , Var, Rec, Row, Empty, type (≈) + , HasType, Lacks, type (.\), type (.+) + , type (.\/), type (.\\), type (.//) + , BiForall, Forall, FreeForall, FreeBiForall + , switch, caseon + -- * Record Construction + , empty + , type (.==), (.==), pattern (:==) + -- ** Restriction + , type (.-), (.-) + -- ** Query + , type (.!), (.!) + -- ** Union + , (.+), Disjoint, pattern (:+) + , (.//) + -- * Variant construction + , pattern IsJust + -- ** Expansion + , diversify + -- ** Destruction + , impossible, trial, trial', multiTrial + -- * Labels + , labels + ) +where + +import Data.Row.Dictionaries +import Data.Row.Variants +import Data.Row.Records +import Data.Row.Switch
+ src/Data/Row/Dictionaries.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row.Dictionaries +-- +-- This module exports various dictionaries that help the type-checker when +-- dealing with row-types. +-- +-- For the various axioms, type variables are consistently in the following order: +-- +-- * Any types that do not belong later. +-- +-- * Labels +-- +-- * Row-types +-- +-- * If applicable, the type in the row-type at the given label goes after +-- each row-type +-- +-- * Constraints +----------------------------------------------------------------------------- + + +module Data.Row.Dictionaries + ( -- * Axioms + uniqueMap, uniqueAp, uniqueApSingle, uniqueZip + , extendHas, mapHas, apHas, apSingleHas + , mapExtendSwap, apExtendSwap, apSingleExtendSwap, zipExtendSwap + , FreeForall + , FreeBiForall + , freeForall + , mapForall + , apSingleForall + -- ** Helper Types + , IsA(..) + , As(..) + , ActsOn(..) + , As'(..) + -- * Re-exports + , Dict(..), (:-)(..), HasDict(..), (\\), withDict + , Unconstrained, Unconstrained1, Unconstrained2 + + ) +where + +import Data.Constraint +import Data.Functor.Const +import Data.Proxy +import qualified Unsafe.Coerce as UNSAFE +import GHC.TypeLits + +import Data.Row.Internal + + + +-- | This data type is used to for its ability to existentially bind a type +-- variable. Particularly, it says that for the type 'a', there exists a 't' +-- such that @a ~ f t@ and @c t@ holds. +data As c f a where + As :: forall c f a t. (a ~ f t, c t) => As c f a + +-- | A class to capture the idea of 'As' so that it can be partially applied in +-- a context. +class IsA c f a where + as :: As c f a + +instance c a => IsA c f (f a) where + as = As + +-- | Like 'As', but here we know the underlying value is some 'f' applied to the +-- given type 'a'. +data As' c t a where + As' :: forall c f a t. (a ~ f t, c f) => As' c t a + +-- | A class to capture the idea of 'As'' so that it can be partially applied in +-- a context. +class ActsOn c t a where + actsOn :: As' c t a + +instance c f => ActsOn c t (f t) where + actsOn = As' + +-- | An internal type used by the 'metamorph' in 'mapForall'. +newtype MapForall c f (r :: Row k) = MapForall { unMapForall :: Dict (Forall (Map f r) (IsA c f)) } + +-- | An internal type used by the 'metamorph' in 'apSingleForall'. +newtype ApSingleForall c a (fs :: Row (k -> k')) = ApSingleForall + { unApSingleForall :: Dict (Forall (ApSingle fs a) (ActsOn c a)) } + +-- | This allows us to derive a @Forall (Map f r) ..@ from a @Forall r ..@. +mapForall :: forall f ρ c. Forall ρ c :- Forall (Map f ρ) (IsA c f) +mapForall = Sub $ unMapForall $ metamorph @_ @ρ @c @Const @Proxy @(MapForall c f) @Proxy Proxy empty uncons cons $ Proxy + where empty _ = MapForall Dict + uncons _ _ = Const Proxy + cons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, FrontExtends ℓ τ ρ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Const (MapForall c f ρ) (Proxy τ) + -> MapForall c f (Extend ℓ τ ρ) + cons _ (Const (MapForall Dict)) = case frontExtendsDict @ℓ @τ @ρ of + FrontExtendsDict Dict -> MapForall Dict + \\ mapExtendSwap @f @ℓ @τ @ρ + \\ uniqueMap @f @(Extend ℓ τ ρ) + +-- | This allows us to derive a @Forall (ApSingle f r) ..@ from a @Forall f ..@. +apSingleForall :: forall a fs c. Forall fs c :- Forall (ApSingle fs a) (ActsOn c a) +apSingleForall = Sub $ unApSingleForall $ metamorph @_ @fs @c @Const @Proxy @(ApSingleForall c a) @Proxy Proxy empty uncons cons $ Proxy + where empty _ = ApSingleForall Dict + uncons _ _ = Const Proxy + cons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, FrontExtends ℓ τ ρ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Const (ApSingleForall c a ρ) (Proxy τ) + -> ApSingleForall c a (Extend ℓ τ ρ) + cons _ (Const (ApSingleForall Dict)) = case frontExtendsDict @ℓ @τ @ρ of + FrontExtendsDict Dict -> ApSingleForall Dict + \\ apSingleExtendSwap @a @ℓ @τ @ρ + \\ uniqueApSingle @a @(Extend ℓ τ ρ) + +-- | Allow any 'Forall' over a row-type, be usable for 'Unconstrained1'. +freeForall :: forall r c. Forall r c :- Forall r Unconstrained1 +freeForall = Sub $ UNSAFE.unsafeCoerce @(Dict (Forall r c)) Dict + +-- | `FreeForall` can be used when a `Forall` constraint is necessary but there +-- is no particular constraint we care about. +type FreeForall r = Forall r Unconstrained1 + +-- | `FreeForall` can be used when a `BiForall` constraint is necessary but +-- there is no particular constraint we care about. +type FreeBiForall r1 r2 = BiForall r1 r2 Unconstrained2 + +-- | If we know that 'r' has been extended with @l .== t@, then we know that this +-- extension at the label 'l' must be 't'. +extendHas :: forall l t r. Dict (Extend l t r .! l ≈ t) +extendHas = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | This allows us to derive @Map f r .! l ≈ f t@ from @r .! l ≈ t@ +mapHas :: forall f l t r. (r .! l ≈ t) :- (Map f r .! l ≈ f t, Map f r .- l ≈ Map f (r .- l)) +mapHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(r .! l ≈ t) + +-- | This allows us to derive @Ap ϕ ρ .! l ≈ f t@ from @ϕ .! l ≈ f@ and @ρ .! l ≈ t@ +apHas :: forall l f ϕ t ρ. (ϕ .! l ≈ f, ρ .! l ≈ t) :- (Ap ϕ ρ .! l ≈ f t, Ap ϕ ρ .- l ≈ Ap (ϕ .- l) (ρ .- l)) +apHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(ϕ .! l ≈ f, ρ .! l ≈ t) + +-- | This allows us to derive @ApSingle r x .! l ≈ f x@ from @r .! l ≈ f@ +apSingleHas :: forall x l f r. (r .! l ≈ f) :- (ApSingle r x .! l ≈ f x, ApSingle r x .- l ≈ ApSingle (r .- l) x) +apSingleHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(r .! l ≈ f) + +-- | Proof that the 'Map' type family preserves labels and their ordering. +mapExtendSwap :: forall f ℓ τ r. Dict (Extend ℓ (f τ) (Map f r) ≈ Map f (Extend ℓ τ r)) +mapExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | Proof that the 'Ap' type family preserves labels and their ordering. +apExtendSwap :: forall ℓ f fs τ r. Dict (Extend ℓ (f τ) (Ap fs r) ≈ Ap (Extend ℓ f fs) (Extend ℓ τ r)) +apExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | Proof that the 'ApSingle' type family preserves labels and their ordering. +apSingleExtendSwap :: forall τ ℓ f r. Dict (Extend ℓ (f τ) (ApSingle r τ) ≈ ApSingle (Extend ℓ f r) τ) +apSingleExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | Proof that the 'Ap' type family preserves labels and their ordering. +zipExtendSwap :: forall ℓ τ1 r1 τ2 r2. Dict (Extend ℓ (τ1, τ2) (Zip r1 r2) ≈ Zip (Extend ℓ τ1 r1) (Extend ℓ τ2 r2)) +zipExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | Map preserves uniqueness of labels. +uniqueMap :: forall f r. Dict (AllUniqueLabels (Map f r) ≈ AllUniqueLabels r) +uniqueMap = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | Ap preserves uniqueness of labels. +uniqueAp :: forall fs r. Dict (AllUniqueLabels (Ap fs r) ≈ AllUniqueLabels r) +uniqueAp = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | ApSingle preserves uniqueness of labels. +uniqueApSingle :: forall x r. Dict (AllUniqueLabels (ApSingle r x) ≈ AllUniqueLabels r) +uniqueApSingle = UNSAFE.unsafeCoerce $ Dict @Unconstrained + +-- | Zip preserves uniqueness of labels. +uniqueZip :: forall r1 r2. Dict (AllUniqueLabels (Zip r1 r2) ≈ (AllUniqueLabels r1, AllUniqueLabels r2)) +uniqueZip = UNSAFE.unsafeCoerce $ Dict @Unconstrained
+ src/Data/Row/Internal.hs view
@@ -0,0 +1,518 @@+{-# LANGUAGE CPP #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE UndecidableSuperClasses #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row.Internal +-- +-- This module implements the internals of open records and variants. +-- +----------------------------------------------------------------------------- + + +module Data.Row.Internal + ( + -- * Rows + Row(..) + , Label(..) + , KnownSymbol + , LT(..) + , Empty + , HideType(..) + -- * Row Operations + , Extend, Modify, Rename + , type (.==), type (.!), type (.-), type (.\\) + -- $merges + , type (.+), type (.\/), type (.//) + -- * Row Constraints + , Lacks, type (.\), HasType + , Forall(..) + , BiForall(..) + , BiConstraint + , Unconstrained + , Unconstrained1 + , Unconstrained2 + , FrontExtends(..) + , FrontExtendsDict(..) + , WellBehaved, AllUniqueLabels + , Ap, ApSingle, Zip, Map, Subset, Disjoint + -- * Helper functions + , Labels, labels, labels' + , show' + , toKey + , type (≈) + ) +where + +import Data.Bifunctor (Bifunctor(..)) +import Data.Constraint +import Data.Functor.Const +import Data.Proxy +import Data.String (IsString (fromString)) +import Data.Text (Text) +import qualified Data.Text as Text +import Data.Type.Equality (type (==)) + +import GHC.OverloadedLabels +import GHC.TypeLits +import qualified GHC.TypeLits as TL + + + + +{-------------------------------------------------------------------- + Rows +--------------------------------------------------------------------} +-- | The kind of rows. This type is only used as a datakind. A row is a typelevel entity telling us +-- which symbols are associated with which types. +newtype Row a = R [LT a] + -- ^ A row is a list of symbol-to-type pairs that should always be sorted + -- lexically by the symbol. + -- The constructor is exported here (because this is an internal module) but + -- should not be exported elsewhere. + +-- | The kind of elements of rows. Each element is a label and its associated type. +data LT a = Symbol :-> a + + +-- | A label +data Label (s :: Symbol) = Label + deriving (Eq) + +instance KnownSymbol s => Show (Label s) where + show = symbolVal + +instance x ≈ y => IsLabel x (Label y) where +#if __GLASGOW_HASKELL__ >= 802 + fromLabel = Label +#else + fromLabel _ = Label +#endif + +-- | A helper function for showing labels +show' :: (IsString s, Show a) => a -> s +show' = fromString . show + +-- | A helper function to turn a Label directly into 'Text'. +toKey :: forall s. KnownSymbol s => Label s -> Text +toKey = Text.pack . symbolVal + +-- | Type level version of 'empty' +type Empty = R '[] + +-- | Elements stored in a Row type are usually hidden. +data HideType where + HideType :: a -> HideType + + + +{-------------------------------------------------------------------- + Row operations +--------------------------------------------------------------------} + +infixl 4 .\ {- This comment needed to appease CPP -} +-- | Does the row lack (i.e. it does not have) the specified label? +type family (r :: Row k) .\ (l :: Symbol) :: Constraint where + R r .\ l = LacksR l r r + +-- | Type level Row extension +type family Extend (l :: Symbol) (a :: k) (r :: Row k) :: Row k where + Extend l a (R x) = R (Inject (l :-> a) x) + +-- | Type level Row modification +type family Modify (l :: Symbol) (a :: k) (r :: Row k) :: Row k where + Modify l a (R ρ) = R (ModifyR l a ρ) + +-- | Type level row renaming +type family Rename (l :: Symbol) (l' :: Symbol) (r :: Row k) :: Row k where + Rename l l' r = Extend l' (r .! l) (r .- l) + +infixl 5 .! +-- | Type level label fetching +type family (r :: Row k) .! (t :: Symbol) :: k where + R r .! l = Get l r + +infixl 6 .- +-- | Type level Row element removal +type family (r :: Row k) .- (s :: Symbol) :: Row k where + R r .- l = R (Remove l r) + +infixl 6 .\\ {- This comment needed to appease CPP -} +-- | Type level Row difference. That is, @l '.\\' r@ is the row remaining after +-- removing any matching elements of @r@ from @l@. +type family (l :: Row k) .\\ (r :: Row k) :: Row k where + R l .\\ R r = R (Diff l r) + +-- $merges +-- == Various row-type merges +-- The difference between '.+' (read "append"), '.\/' (read "min-join"), and +-- '.\\' (read "const-union") comes down to how duplicates are handled. +-- In '.+', the two given row-types must be entirely unique. Even the same +-- entry in both row-types is forbidden. In '.\/', this final restriction is +-- relaxed, allowing two row-types that have no conflicts to be merged in the +-- logical way. The '.\\' operator is the most liberal, allowing any two row-types +-- to be merged together, and whenever there is a conflict, favoring the left argument. +-- +-- As examples of use: +-- +-- - '.+' is used when appending two records, assuring that those two records are +-- entirely disjoint. +-- +-- - '.\/' is used when diversifying a variant, allowing some extension to the +-- row-type so long as no original types have changed. +-- +-- - './/' is used when doing record overwrite, allowing data in a record to +-- totally overwrite what was previously there. + +infixl 6 .+ +-- | Type level Row append +type family (l :: Row k) .+ (r :: Row k) :: Row k where + R l .+ R r = R (Merge l r) + +infixl 6 .\/ +-- | The minimum join of the two rows. +type family (l :: Row k) .\/ (r :: Row k) where + R l .\/ R r = R (MinJoinR l r) + +infixl 6 .// +-- | The overwriting union, where the left row overwrites the types of the right +-- row where the labels overlap. +type family (l :: Row k) .// (r :: Row k) where + R l .// R r = R (ConstUnionR l r) + + +{-------------------------------------------------------------------- + Syntactic sugar for record operations +--------------------------------------------------------------------} +-- | Alias for '.\'. It is a class rather than an alias, so that +-- it can be partially applied. +class Lacks (l :: Symbol) (r :: Row *) +instance (r .\ l) => Lacks l r + + +-- | Alias for @(r .! l) ≈ a@. It is a class rather than an alias, so that +-- it can be partially applied. +class (r .! l ≈ a) => HasType l a r +instance (r .! l ≈ a) => HasType l a r + +-- | A type level way to create a singleton Row. +infix 7 .== +type (l :: Symbol) .== (a :: k) = Extend l a Empty + + +{-------------------------------------------------------------------- + Constrained record operations +--------------------------------------------------------------------} + +-- | A dictionary of information that proves that extending a row-type @r@ with +-- a label @l@ will necessarily put it to the front of the underlying row-type +-- list. This is quite internal and should not generally be necessary. +data FrontExtendsDict l t r = forall ρ. FrontExtendsDict (Dict (r ~ R ρ, R (l :-> t ': ρ) ≈ Extend l t (R ρ), AllUniqueLabelsR (l :-> t ': ρ))) + +-- | A class wrapper for 'FrontExtendsDict'. +class FrontExtends l t r where + frontExtendsDict :: FrontExtendsDict l t r + +instance (r ~ R ρ, R (l :-> t ': ρ) ≈ Extend l t (R ρ), AllUniqueLabelsR (l :-> t ': ρ)) => FrontExtends l t r where + frontExtendsDict = FrontExtendsDict Dict + + +-- | Any structure over a row in which every element is similarly constrained can +-- be metamorphized into another structure over the same row. +class Forall (r :: Row k) (c :: k -> Constraint) where + -- | A metamorphism is an anamorphism (an unfold) followed by a catamorphism (a fold). + -- The parameter 'p' describes the output of the unfold and the input of the fold. + -- For records, @p = (,)@, because every entry in the row will unfold to a value paired + -- with the rest of the record. + -- For variants, @p = Either@, because there will either be a value or future types to + -- explore. + -- 'Const' can be useful when the types in the row are unnecessary. + metamorph :: forall (p :: * -> * -> *) (f :: Row k -> *) (g :: Row k -> *) (h :: k -> *). Bifunctor p + => Proxy (Proxy h, Proxy p) + -> (f Empty -> g Empty) + -- ^ The way to transform the empty element + -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> f ρ -> p (f (ρ .- ℓ)) (h τ)) + -- ^ The unfold + -> (forall ℓ τ ρ. (KnownSymbol ℓ, c τ, FrontExtends ℓ τ ρ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> p (g ρ) (h τ) -> g (Extend ℓ τ ρ)) + -- ^ The fold + -> f r -- ^ The input structure + -> g r + +instance Forall (R '[]) c where + {-# INLINE metamorph #-} + metamorph _ empty _ _ = empty + +instance (KnownSymbol ℓ, c τ, Forall ('R ρ) c, FrontExtends ℓ τ ('R ρ), AllUniqueLabels (Extend ℓ τ ('R ρ))) => Forall ('R (ℓ :-> τ ': ρ) :: Row k) c where + {-# INLINE metamorph #-} + metamorph h empty uncons cons = case frontExtendsDict @ℓ @τ @('R ρ) of + FrontExtendsDict Dict -> + cons (Label @ℓ) . first (metamorph @_ @('R ρ) @c h empty uncons cons) . uncons (Label @ℓ) + + +-- | Any structure over two rows in which the elements of each row satisfy some +-- constraints can be metamorphized into another structure over both of the +-- rows. +class BiForall (r1 :: Row k1) (r2 :: Row k2) (c :: k1 -> k2 -> Constraint) where + -- | A metamorphism is an anamorphism (an unfold) followed by a catamorphism (a fold). + biMetamorph :: forall (p :: * -> * -> *) (f :: Row k1 -> Row k2 -> *) (g :: Row k1 -> Row k2 -> *) + (h :: k1 -> k2 -> *). Bifunctor p + => Proxy (Proxy h, Proxy p) + -> (f Empty Empty -> g Empty Empty) + -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2, HasType ℓ τ1 ρ1, HasType ℓ τ2 ρ2) + => Label ℓ -> f ρ1 ρ2 -> p (f (ρ1 .- ℓ) (ρ2 .- ℓ)) (h τ1 τ2)) + -> (forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, c τ1 τ2, FrontExtends ℓ τ1 ρ1, FrontExtends ℓ τ2 ρ2, AllUniqueLabels (Extend ℓ τ1 ρ1), AllUniqueLabels (Extend ℓ τ2 ρ2)) + => Label ℓ -> p (g ρ1 ρ2) (h τ1 τ2) -> g (Extend ℓ τ1 ρ1) (Extend ℓ τ2 ρ2)) + -> f r1 r2 -> g r1 r2 + + +instance BiForall (R '[]) (R '[]) c1 where + {-# INLINE biMetamorph #-} + biMetamorph _ empty _ _ = empty + +instance (KnownSymbol ℓ, c τ1 τ2, BiForall ('R ρ1) ('R ρ2) c, FrontExtends ℓ τ1 ('R ρ1), FrontExtends ℓ τ2 ('R ρ2), AllUniqueLabels (Extend ℓ τ1 ('R ρ1)), AllUniqueLabels (Extend ℓ τ2 ('R ρ2))) + => BiForall ('R (ℓ :-> τ1 ': ρ1)) ('R (ℓ :-> τ2 ': ρ2)) c where + {-# INLINE biMetamorph #-} + biMetamorph h empty uncons cons = case (frontExtendsDict @ℓ @τ1 @('R ρ1), frontExtendsDict @ℓ @τ2 @('R ρ2)) of + (FrontExtendsDict Dict, FrontExtendsDict Dict) -> + cons (Label @ℓ) . first (biMetamorph @_ @_ @('R ρ1) @('R ρ2) @c h empty uncons cons) . uncons (Label @ℓ) + + +-- | A null constraint +class Unconstrained +instance Unconstrained + +-- | A null constraint of one argument +class Unconstrained1 a +instance Unconstrained1 a + +-- | A null constraint of two arguments +class Unconstrained2 a b +instance Unconstrained2 a b + +-- | A pair of constraints +class (c1 x, c2 y) => BiConstraint c1 c2 x y +instance (c1 x, c2 y) => BiConstraint c1 c2 x y + +-- | The labels in a Row. +type family Labels (r :: Row a) where + Labels (R '[]) = '[] + Labels (R (l :-> a ': xs)) = l ': Labels (R xs) + +-- | Return a list of the labels in a row type. +labels :: forall ρ c s. (IsString s, Forall ρ c) => [s] +labels = getConst $ metamorph @_ @ρ @c @Const @(Const ()) @(Const [s]) @Proxy Proxy (const $ Const []) doUncons doCons (Const ()) + where doUncons _ _ = Const $ Const () + doCons l (Const (Const c)) = Const $ show' l : c + +-- | Return a list of the labels in a row type and is specialized to the 'Unconstrained1' constraint. +labels' :: forall ρ s. (IsString s, Forall ρ Unconstrained1) => [s] +labels' = labels @ρ @Unconstrained1 + + +{-------------------------------------------------------------------- + Convenient type families and classes +--------------------------------------------------------------------} + +-- | A convenient way to provide common, easy constraints +type WellBehaved ρ = (Forall ρ Unconstrained1, AllUniqueLabels ρ) + +-- | Are all of the labels in this Row unique? +type family AllUniqueLabels (r :: Row k) :: Constraint where + AllUniqueLabels (R r) = AllUniqueLabelsR r + +type family AllUniqueLabelsR (r :: [LT k]) :: Constraint where + AllUniqueLabelsR '[] = Unconstrained + AllUniqueLabelsR '[l :-> a] = Unconstrained + AllUniqueLabelsR (l :-> a ': l :-> b ': _) = TypeError + (TL.Text "The label " :<>: ShowType l :<>: TL.Text " is not unique." + :$$: TL.Text "It is assigned to both " :<>: ShowType a :<>: TL.Text " and " :<>: ShowType b) + AllUniqueLabelsR (l :-> a ': l' :-> b ': r) = AllUniqueLabelsR (l' :-> b ': r) + +-- | Is the first row a subset of the second? +type family Subset (r1 :: Row k) (r2 :: Row k) :: Constraint where + Subset (R r1) (R r2) = SubsetR r1 r2 + +type family SubsetR (r1 :: [LT k]) (r2 :: [LT k]) :: Constraint where + SubsetR '[] _ = Unconstrained + SubsetR x '[] = TypeError (TL.Text "One row-type is not a subset of the other." + :$$: TL.Text "The first contains the bindings " :<>: ShowRowType x + :<>: TL.Text " while the second does not.") + SubsetR (l :-> a ': x) (l :-> a ': y) = SubsetR x y + SubsetR (l :-> a ': x) (l :-> b ': y) = + TypeError (TL.Text "One row-type is not a subset of the other." + :$$: TL.Text "The first assigns the label " :<>: ShowType l :<>: TL.Text " to " + :<>: ShowType a :<>: TL.Text " while the second assigns it to " :<>: ShowType b) + SubsetR (hl :-> al ': tl) (hr :-> ar ': tr) = + Ifte (hl <=.? hr) + (TypeError (TL.Text "One row-type is not a subset of the other." + :$$: TL.Text "The first assigns the label " :<>: ShowType hl :<>: TL.Text " to " + :<>: ShowType al :<>: TL.Text " while the second has no assignment for it.")) + (SubsetR (hl :-> al ': tl) tr) + +-- | A type synonym for disjointness. +type Disjoint l r = ( WellBehaved l + , WellBehaved r + , Subset l (l .+ r) + , Subset r (l .+ r) + , l .+ r .\\ l ≈ r + , l .+ r .\\ r ≈ l) + +-- | Map a type level function over a Row. +type family Map (f :: a -> b) (r :: Row a) :: Row b where + Map f (R r) = R (MapR f r) + +type family MapR (f :: a -> b) (r :: [LT a]) :: [LT b] where + MapR f '[] = '[] + MapR f (l :-> v ': t) = l :-> f v ': MapR f t + +-- | Take two rows with the same labels, and apply the type operator from the +-- first row to the type of the second. +type family Ap (fs :: Row (a -> b)) (r :: Row a) :: Row b where + Ap (R fs) (R r) = R (ApR fs r) + +type family ApR (fs :: [LT (a -> b)]) (r :: [LT a]) :: [LT b] where + ApR '[] '[] = '[] + ApR (l :-> f ': tf) (l :-> v ': tv) = l :-> f v ': ApR tf tv + ApR _ _ = TypeError (TL.Text "Row types with different label sets cannot be App'd together.") + +-- | Take a row of type operators and apply each to the second argument. +type family ApSingle (fs :: Row (a -> b)) (x :: a) :: Row b where + ApSingle (R fs) x = R (ApSingleR fs x) + +type family ApSingleR (fs :: [LT (a -> b)]) (x :: a) :: [LT b] where + ApSingleR '[] _ = '[] + ApSingleR (l ':-> f ': fs) x = l ':-> f x ': ApSingleR fs x + +-- | Zips two rows together to create a Row of the pairs. +-- The two rows must have the same set of labels. +type family Zip (r1 :: Row *) (r2 :: Row *) where + Zip (R r1) (R r2) = R (ZipR r1 r2) + +type family ZipR (r1 :: [LT *]) (r2 :: [LT *]) where + ZipR '[] '[] = '[] + ZipR (l :-> t1 ': r1) (l :-> t2 ': r2) = + l :-> (t1, t2) ': ZipR r1 r2 + ZipR (l :-> t1 ': r1) _ = TypeError (TL.Text "Row types with different label sets cannot be zipped" + :$$: TL.Text "For one, the label " :<>: ShowType l :<>: TL.Text " is not in both lists.") + ZipR '[] (l :-> t ': r) = TypeError (TL.Text "Row types with different label sets cannot be zipped" + :$$: TL.Text "For one, the label " :<>: ShowType l :<>: TL.Text " is not in both lists.") + +type family Inject (l :: LT k) (r :: [LT k]) where + Inject (l :-> t) '[] = (l :-> t ': '[]) + Inject (l :-> t) (l :-> t' ': x) = TypeError (TL.Text "Cannot inject a label into a row type that already has that label" + :$$: TL.Text "The label " :<>: ShowType l :<>: TL.Text " was already assigned the type " + :<>: ShowType t' :<>: TL.Text " and is now trying to be assigned the type " + :<>: ShowType t :<>: TL.Text ".") + Inject (l :-> t) (l' :-> t' ': x) = + Ifte (l <=.? l') + (l :-> t ': l' :-> t' ': x) + (l' :-> t' ': Inject (l :-> t) x) + +-- | Type level Row modification helper +type family ModifyR (l :: Symbol) (a :: k) (ρ :: [LT k]) :: [LT k] where + ModifyR l a (l :-> a' ': ρ) = l :-> a ': ρ + ModifyR l a (l' :-> a' ': ρ) = l' :-> a' ': ModifyR l a ρ + ModifyR l a '[] = TypeError (TL.Text "Tried to modify the label " :<>: ShowType l + :<>: TL.Text ", but it does not appear in the row-type.") + +type family Ifte (c :: Bool) (t :: k) (f :: k) where + Ifte True t f = t + Ifte False t f = f + +type family Get (l :: Symbol) (r :: [LT k]) where + Get l '[] = TypeError (TL.Text "No such field: " :<>: ShowType l) + Get l (l :-> t ': x) = t + Get l (l' :-> t ': x) = Get l x + +type family Remove (l :: Symbol) (r :: [LT k]) where + Remove l r = RemoveT l r r + +type family RemoveT (l :: Symbol) (r :: [LT k]) (r_orig :: [LT k]) where + RemoveT l (l :-> t ': x) _ = x + RemoveT l (l' :-> t ': x) r = l' :-> t ': RemoveT l x r + RemoveT l '[] r = TypeError (TL.Text "Cannot remove a label that does not occur in the row type." + :$$: TL.Text "The label " :<>: ShowType l :<>: TL.Text " is not in " + :<>: ShowRowType r) + +type family LacksR (l :: Symbol) (r :: [LT k]) (r_orig :: [LT k]) :: Constraint where + LacksR l '[] _ = Unconstrained + LacksR l (l :-> t ': x) r = TypeError (TL.Text "The label " :<>: ShowType l + :<>: TL.Text " already exists in " :<>: ShowRowType r) + LacksR l (l' :-> _ ': x) r = Ifte (l <=.? l') Unconstrained (LacksR l x r) + + +type family Merge (l :: [LT k]) (r :: [LT k]) where + Merge '[] r = r + Merge l '[] = l + Merge (h :-> a ': tl) (h :-> a ': tr) = + TypeError (TL.Text "The label " :<>: ShowType h :<>: TL.Text " (of type " + :$$: ShowType a :<>: TL.Text ") has duplicate assignments.") + Merge (h :-> a ': tl) (h :-> b ': tr) = + TypeError (TL.Text "The label " :<>: ShowType h :<>: TL.Text " has conflicting assignments." + :$$: TL.Text "Its type is both " :<>: ShowType a :<>: TL.Text " and " :<>: ShowType b :<>: TL.Text ".") + Merge (hl :-> al ': tl) (hr :-> ar ': tr) = + Ifte (hl <=.? hr) + (hl :-> al ': Merge tl (hr :-> ar ': tr)) + (hr :-> ar ': Merge (hl :-> al ': tl) tr) + +type family MinJoinR (l :: [LT k]) (r :: [LT k]) where + MinJoinR '[] r = r + MinJoinR l '[] = l + MinJoinR (h :-> a ': tl) (h :-> a ': tr) = + (h :-> a ': MinJoinR tl tr) + MinJoinR (h :-> a ': tl) (h :-> b ': tr) = + TypeError (TL.Text "The label " :<>: ShowType h :<>: TL.Text " has conflicting assignments." + :$$: TL.Text "Its type is both " :<>: ShowType a :<>: TL.Text " and " :<>: ShowType b :<>: TL.Text ".") + MinJoinR (hl :-> al ': tl) (hr :-> ar ': tr) = + Ifte (CmpSymbol hl hr == 'LT) + (hl :-> al ': MinJoinR tl (hr :-> ar ': tr)) + (hr :-> ar ': MinJoinR (hl :-> al ': tl) tr) + +type family ConstUnionR (l :: [LT k]) (r :: [LT k]) where + ConstUnionR '[] r = r + ConstUnionR l '[] = l + ConstUnionR (h :-> a ': tl) (h :-> b ': tr) = + (h :-> a ': ConstUnionR tl tr) + ConstUnionR (hl :-> al ': tl) (hr :-> ar ': tr) = + Ifte (CmpSymbol hl hr == 'LT) + (hl :-> al ': ConstUnionR tl (hr :-> ar ': tr)) + (hr :-> ar ': ConstUnionR (hl :-> al ': tl) tr) + + +-- | Returns the left list with all of the elements from the right list removed. +type family Diff (l :: [LT k]) (r :: [LT k]) where + Diff '[] r = '[] + Diff l '[] = l + Diff (l :-> al ': tl) (l :-> al ': tr) = Diff tl tr + Diff (hl :-> al ': tl) (hr :-> ar ': tr) = + Ifte (hl <=.? hr) + (hl :-> al ': Diff tl (hr :-> ar ': tr)) + (Diff (hl :-> al ': tl) tr) + +type family ShowRowType (r :: [LT k]) :: ErrorMessage where + ShowRowType '[] = TL.Text "Empty" + ShowRowType '[l ':-> t] = TL.ShowType l TL.:<>: TL.Text " .== " TL.:<>: TL.ShowType t + ShowRowType ((l ':-> t) ': r) = TL.ShowType l TL.:<>: TL.Text " .== " TL.:<>: TL.ShowType t TL.:<>: TL.Text " .+ " TL.:<>: ShowRowType r + +-- | There doesn't seem to be a (<=.?) :: Symbol -> Symbol -> Bool, +-- so here it is in terms of other ghc-7.8 type functions +type a <=.? b = (CmpSymbol a b == 'LT) + +-- | A lower fixity operator for type equality +infix 4 ≈ +type a ≈ b = a ~ b
+ src/Data/Row/Records.hs view
@@ -0,0 +1,837 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE UndecidableInstances #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row.Records +-- +-- This module implements extensible records using closed type famillies. +-- +-- See Examples.lhs for examples. +-- +-- Lists of (label,type) pairs are kept sorted thereby ensuring +-- that { x = 0, y = 0 } and { y = 0, x = 0 } have the same type. +-- +-- In this way we can implement standard type classes such as Show, Eq, Ord and Bounded +-- for open records, given that all the elements of the open record satify the constraint. +-- +----------------------------------------------------------------------------- + + +module Data.Row.Records + ( + -- * Types and constraints + Label(..) + , KnownSymbol, AllUniqueLabels, WellBehaved + , Rec, Row, Empty, type (≈) + -- * Construction + , empty + , type (.==), (.==), pattern (:==), unSingleton + , default', defaultA + , fromLabels, fromLabelsA, fromLabelsMapA + -- ** Extension + , extend, Extend, Lacks, type (.\) + -- ** Restriction + , type (.-), (.-) + , lazyRemove + , restrict, split + -- ** Modification + , update, focus, multifocus, Modify, rename, Rename + -- * Query + , HasType, type (.!), (.!) + -- * Combine + -- ** Disjoint union + , type (.+), (.+), Disjoint, pattern (:+) + -- ** Overwrite + , type (.//), (.//) + -- * Application with functions + , curryRec + , (.$) + -- * Native Conversion + -- $native + , fromNative, toNative, toNativeGeneral + , FromNative, ToNative, ToNativeGeneral + , NativeRow + -- * Dynamic Conversion + , toDynamicMap, fromDynamicMap + -- * Row operations + -- ** Map + , Map, map, map', mapF + , transform, transform' + , zipTransform, zipTransform' + -- ** Fold + , BiForall, Forall, erase, eraseWithLabels, eraseZip, eraseToHashMap + -- ** Zip + , Zip, zip + -- ** Applicative-like functions + , traverse, traverseMap + , sequence, sequence' + , distribute + -- ** Compose + -- $compose + , compose, uncompose + , compose', uncompose' + -- ** Labels + , labels, labels' + -- ** Coerce + , coerceRec + ) +where + +import Prelude hiding (map, sequence, traverse, zip) + +import Control.DeepSeq (NFData(..), deepseq) + +import Data.Bifunctor (Bifunctor(..)) +import Data.Coerce +import Data.Dynamic +import Data.Functor.Compose +import Data.Functor.Const +import Data.Functor.Identity +import Data.Functor.Product +import Data.Generics.Product.Fields (HasField(..), HasField'(..)) +import Data.Hashable +import Data.HashMap.Lazy (HashMap) +import qualified Data.HashMap.Lazy as M +import qualified Data.List as L +import Data.Monoid (Endo(..), appEndo) +import Data.Proxy +import Data.String (IsString) +import Data.Text (Text) + +import qualified GHC.Generics as G +import GHC.TypeLits +import Unsafe.Coerce + +import Data.Row.Dictionaries +import Data.Row.Internal + + +{-------------------------------------------------------------------- + Open records +--------------------------------------------------------------------} +-- | A record with row r. +newtype Rec (r :: Row *) where + OR :: HashMap Text HideType -> Rec r + +instance Forall r Show => Show (Rec r) where + showsPrec p r = + case eraseWithLabels @Show (showsPrec 7) r of + [] -> + showString "empty" + xs -> + showParen + (p > 6) + (appEndo $ foldMap Endo (L.intersperse (showString " .+ ") (L.map binds xs))) + where + binds (label, value) = + showChar '#' . + showString label . + showString " .== " . + value + +instance Forall r Eq => Eq (Rec r) where + r == r' = and $ eraseZip @Eq (==) r r' + +instance (Forall r Eq, Forall r Ord) => Ord (Rec r) where + compare m m' = cmp $ eraseZip @Ord compare m m' + where cmp l | [] <- l' = EQ + | a : _ <- l' = a + where l' = dropWhile (== EQ) l + +instance (Forall r Bounded, AllUniqueLabels r) => Bounded (Rec r) where + minBound = default' @Bounded minBound + maxBound = default' @Bounded maxBound + +instance Forall r NFData => NFData (Rec r) where + rnf r = getConst $ metamorph @_ @r @NFData @(,) @Rec @(Const ()) @Identity Proxy empty doUncons doCons r + where empty = const $ Const () + doUncons l = second Identity . lazyUncons l + doCons _ (r, x) = deepseq x $ deepseq r $ Const () + +-- | The empty record +empty :: Rec Empty +empty = OR M.empty + +-- | The singleton record +infix 7 .== +(.==) :: KnownSymbol l => Label l -> a -> Rec (l .== a) +l .== a = extend l a empty + +-- | A pattern for the singleton record; can be used to both destruct a record +-- when in a pattern position or construct one in an expression position. +{-# COMPLETE (:==) #-} +infix 7 :== +pattern (:==) :: forall l a. KnownSymbol l => Label l -> a -> Rec (l .== a) +pattern l :== a <- (unSingleton @l @a -> (l, a)) where + (:==) l a = l .== a + +-- | Turns a singleton record into a pair of the label and value. +unSingleton :: forall l a. KnownSymbol l => Rec (l .== a) -> (Label l, a) +unSingleton r = (l, r .! l) where l = Label @l + +{-------------------------------------------------------------------- + Basic record operations +--------------------------------------------------------------------} + + +-- | Record extension. The row may already contain the label, +-- in which case the origin value can be obtained after restriction ('.-') with +-- the label. +extend :: forall a l r. KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) +extend (toKey -> l) a (OR m) = OR $ M.insert l (HideType a) m + +-- | Update the value associated with the label. +update :: (KnownSymbol l, r .! l ≈ a) => Label l -> a -> Rec r -> Rec r +update (toKey -> l) a (OR m) = OR $ M.adjust f l m where f = const (HideType a) + +-- | Focus on the value associated with the label. +focus :: + ( KnownSymbol l + , r' .! l ≈ b + , r .! l ≈ a + , r' ~ Modify l b r + , r ~ Modify l a r' + , Functor f) + => Label l -> (a -> f b) -> Rec r -> f (Rec r') +focus (toKey -> l) f (OR m) = case m M.! l of + HideType x -> OR . flip (M.insert l) m . HideType <$> f (unsafeCoerce x) + +-- | Focus on a sub-record +multifocus :: forall u v r f. + ( Functor f + , Disjoint u r + , Disjoint v r) + => (Rec u -> f (Rec v)) -> Rec (u .+ r) -> f (Rec (v .+ r)) +multifocus f (u :+ r) = (.+ r) <$> f u + +-- | Rename a label. +rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) +rename (toKey -> l) (toKey -> l') (OR m) = OR $ M.insert l' (m M.! l) $ M.delete l m + +-- | Record selection +(.!) :: KnownSymbol l => Rec r -> Label l -> r .! l +OR m .! (toKey -> a) = case m M.! a of + HideType x -> unsafeCoerce x + +infixl 6 .- +-- | Record restriction. Remove the label l from the record. +(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r .- l) +-- OR m .- _ = OR m +OR m .- (toKey -> a) = OR $ M.delete a m + +-- | Record disjoint union (commutative) +infixl 6 .+ +(.+) :: forall l r. FreeForall l => Rec l -> Rec r -> Rec (l .+ r) +OR l .+ OR r = OR $ M.unionWithKey choose l r + where + choose k lv rv = if k `elem` labels' @l @Text then lv else rv + +-- | Record overwrite. +-- +-- The operation @r .// r'@ creates a new record such that: +-- +-- - Any label that is in both @r@ and @r'@ is in the resulting record with the +-- type and value given by the fields in @r@, +-- +-- - Any label that is only found in @r@ is in the resulting record. +-- +-- - Any label that is only found in @r'@ is in the resulting record. +-- +-- This can be thought of as @r@ "overwriting" @r'@. +(.//) :: Rec r -> Rec r' -> Rec (r .// r') +OR l .// OR r = OR $ M.union l r + +-- | A pattern version of record union, for use in pattern matching. +{-# COMPLETE (:+) #-} +infixl 6 :+ +pattern (:+) :: forall l r. Disjoint l r => Rec l -> Rec r -> Rec (l .+ r) +pattern l :+ r <- (split @l -> (l, r)) where + (:+) l r = l .+ r + +-- | Split a record into two sub-records. +split :: forall s r. (Subset s r, FreeForall s) + => Rec r -> (Rec s, Rec (r .\\ s)) +split (OR m) = (OR $ M.intersection m labelMap, OR $ M.difference m labelMap) + where + labelMap = M.fromList $ L.zip (labels' @s) (repeat ()) + +-- | Arbitrary record restriction. Turn a record into a subset of itself. +restrict :: forall r r'. (FreeForall r, Subset r r') => Rec r' -> Rec r +restrict = fst . split + +-- | Removes a label from the record but does not remove the underlying value. +-- +-- This is faster than regular record removal ('.-'), but it has two downsides: +-- +-- 1. It may incur a performance penalty during a future merge operation ('.+'), and +-- +-- 2. It will keep the reference to the value alive, meaning that it will not get garbage collected. +-- +-- Thus, it's great when one knows ahead of time that no future merges will happen +-- and that the whole record will be GC'd soon, for instance, during the catamorphism +-- function of 'metamorph'. +lazyRemove :: KnownSymbol l => Label l -> Rec r -> Rec (r .- l) +lazyRemove _ (OR m) = OR m + +-- | This is the same as @(lazyRemove l r, r .! l)@. +lazyUncons :: KnownSymbol l => Label l -> Rec r -> (Rec (r .- l), r .! l) +lazyUncons l r = (lazyRemove l r, r .! l) + +-- | Kind of like 'curry' for functions over records. +curryRec :: forall l t r x. KnownSymbol l => Label l -> (Rec (l .== t .+ r) -> x) -> t -> Rec r -> x +curryRec l f t r = f $ (l .== t) .+ r + +infixl 2 .$ +-- | This function allows one to do partial application on a function of a record. +-- Note that this also means that arguments can be supplied in arbitrary order. +-- For instance, if one had a function like +-- +-- > xtheny r = (r .! #x) <> (r .! #y) +-- +-- and a record like +-- +-- > greeting = #x .== "hello " .+ #y .== "world!" +-- +-- Then all of the following would be possible: +-- +-- >>> xtheny greeting +-- "hello world!" +-- +-- >>> xtheny .$ (#x, greeting) .$ (#y, greeting) $ empty +-- "hello world!" +-- +-- >>> xtheny .$ (#y, greeting) .$ (#x, greeting) $ empty +-- "hello world!" +-- +-- >>> xtheny .$ (#y, greeting) .$ (#x, #x .== "Goodbye ") $ empty +-- "Goodbye world!" +(.$) :: (KnownSymbol l, r' .! l ≈ t) => (Rec (l .== t .+ r) -> x) -> (Label l, Rec r') -> Rec r -> x +(.$) f (l, r') r = curryRec l f (r' .! l) r + +{-------------------------------------------------------------------- + Folds and maps +--------------------------------------------------------------------} +-- An easy type synonym for a pair where both elements are the same type. +newtype Pair' a = Pair' { unPair' :: (a,a) } + +-- | A standard fold +erase :: forall c ρ b. Forall ρ c => (forall a. c a => a -> b) -> Rec ρ -> [b] +erase f = fmap (snd @String) . eraseWithLabels @c f + +-- | A fold with labels +eraseWithLabels :: forall c ρ s b. (Forall ρ c, IsString s) => (forall a. c a => a -> b) -> Rec ρ -> [(s,b)] +eraseWithLabels f = getConst . metamorph @_ @ρ @c @(,) @Rec @(Const [(s,b)]) @Identity Proxy doNil doUncons doCons + where doNil _ = Const [] + doUncons l = second Identity . lazyUncons l + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> (Const [(s,b)] ρ, Identity τ) -> Const [(s,b)] (Extend ℓ τ ρ) + doCons l (Const c, Identity x) = Const $ (show' l, f x) : c + +-- | A fold over two row type structures at once +eraseZip :: forall c ρ b. Forall ρ c => (forall a. c a => a -> a -> b) -> Rec ρ -> Rec ρ -> [b] +eraseZip f x y = getConst $ metamorph @_ @ρ @c @(,) @(Product Rec Rec) @(Const [b]) @Pair' Proxy (const $ Const []) doUncons doCons (Pair x y) + where doUncons l (Pair r1 r2) = (Pair r1' r2', Pair' (a, b)) + where (r1', a) = lazyUncons l r1 + (r2', b) = lazyUncons l r2 + doCons :: forall ℓ τ ρ. c τ + => Label ℓ -> (Const [b] ρ, Pair' τ) -> Const [b] (Extend ℓ τ ρ) + doCons _ (Const c, unPair' -> x) = Const $ uncurry f x : c + +-- | Turns a record into a 'HashMap' from values representing the labels to +-- the values of the record. +eraseToHashMap :: forall c r s b. (IsString s, Eq s, Hashable s, Forall r c) => + (forall a . c a => a -> b) -> Rec r -> HashMap s b +eraseToHashMap f r = M.fromList $ eraseWithLabels @c f r + +-- | RMap is used internally as a type level lambda for defining record maps. +newtype RMap (f :: * -> *) (ρ :: Row *) = RMap { unRMap :: Rec (Map f ρ) } +newtype RMap2 (f :: * -> *) (g :: * -> *) (ρ :: Row *) = RMap2 { unRMap2 :: Rec (Map f (Map g ρ)) } + +-- | A function to map over a record given a constraint. +map :: forall c f r. Forall r c => (forall a. c a => a -> f a) -> Rec r -> Rec (Map f r) +map f = unRMap . metamorph @_ @r @c @(,) @Rec @(RMap f) @f Proxy doNil doUncons doCons + where + doNil _ = RMap empty + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> Rec ρ -> (Rec (ρ .- ℓ), f τ) + doUncons l = second f . lazyUncons l + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> (RMap f ρ, f τ) -> RMap f (Extend ℓ τ ρ) + doCons l (RMap r, v) = RMap (extend l v r) + \\ mapExtendSwap @f @ℓ @τ @ρ + +newtype RFMap (g :: k1 -> k2) (ϕ :: Row (k2 -> *)) (ρ :: Row k1) = RFMap { unRFMap :: Rec (Ap ϕ (Map g ρ)) } +newtype RecAp (ϕ :: Row (k -> *)) (ρ :: Row k) = RecAp (Rec (Ap ϕ ρ)) +newtype App (f :: k -> *) (a :: k) = App (f a) + +-- | A function to map over a Ap record given constraints. +mapF :: forall k c g (ϕ :: Row (k -> *)) (ρ :: Row k). BiForall ϕ ρ c + => (forall h a. (c h a) => h a -> h (g a)) + -> Rec (Ap ϕ ρ) + -> Rec (Ap ϕ (Map g ρ)) +mapF f = unRFMap . biMetamorph @_ @_ @ϕ @ρ @c @(,) @RecAp @(RFMap g) @App Proxy doNil doUncons doCons . RecAp + where + doNil _ = RFMap empty + doUncons :: forall ℓ f τ ϕ ρ. (KnownSymbol ℓ, c f τ, HasType ℓ f ϕ, HasType ℓ τ ρ) + => Label ℓ -> RecAp ϕ ρ -> (RecAp (ϕ .- ℓ) (ρ .- ℓ), App f τ) + doUncons l (RecAp r) = bimap RecAp App $ lazyUncons l r + \\ apHas @ℓ @f @ϕ @τ @ρ + doCons :: forall ℓ f τ ϕ ρ. (KnownSymbol ℓ, c f τ) + => Label ℓ -> (RFMap g ϕ ρ, App f τ) -> RFMap g (Extend ℓ f ϕ) (Extend ℓ τ ρ) + doCons l (RFMap r, App v) = RFMap (extend l (f @f @τ v) r) + \\ mapExtendSwap @g @ℓ @τ @ρ + \\ apExtendSwap @ℓ @f @ϕ @(g τ) @(Map g ρ) + +-- | A function to map over a record given no constraint. +map' :: forall f r. FreeForall r => (forall a. a -> f a) -> Rec r -> Rec (Map f r) +map' = map @Unconstrained1 + +-- | Lifts a natural transformation over a record. In other words, it acts as a +-- record transformer to convert a record of @f a@ values to a record of @g a@ +-- values. If no constraint is needed, instantiate the first type argument with +-- 'Unconstrained1' or use 'transform''. +transform :: forall c r (f :: * -> *) (g :: * -> *). Forall r c => (forall a. c a => f a -> g a) -> Rec (Map f r) -> Rec (Map g r) +transform f = unRMap . metamorph @_ @r @c @(,) @(RMap f) @(RMap g) @f Proxy doNil doUncons doCons . RMap + where + doNil _ = RMap empty + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> RMap f ρ -> (RMap f (ρ .- ℓ), f τ) + doUncons l (RMap r) = first RMap $ lazyUncons l r + \\ mapHas @f @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> (RMap g ρ, f τ) -> RMap g (Extend ℓ τ ρ) + doCons l (RMap r, v) = RMap (extend l (f v) r) + \\ mapExtendSwap @g @ℓ @τ @ρ + +-- | A version of 'transform' for when there is no constraint. +transform' :: forall r (f :: * -> *) (g :: * -> *). FreeForall r => (forall a. f a -> g a) -> Rec (Map f r) -> Rec (Map g r) +transform' = transform @Unconstrained1 @r + + +data RecMapPair (f :: * -> *) (g :: * -> *) (ρ :: Row *) = RecMapPair (Rec (Map f ρ)) (Rec (Map g ρ)) + +-- | Zip together two records that are the same up to the type being mapped over them, +-- combining their constituent fields with the given function. +zipTransform :: forall c r (f :: * -> *) (g :: * -> *) (h :: * -> *) . + Forall r c => (forall a. c a => f a -> g a -> h a) -> Rec (Map f r) -> Rec (Map g r) -> Rec (Map h r) +zipTransform f x y = unRMap $ metamorph @_ @r @c @(,) @(RecMapPair f g) @(RMap h) @h Proxy doNil doUncons doCons $ RecMapPair x y + where + doNil _ = RMap empty + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> RecMapPair f g ρ -> (RecMapPair f g (ρ .- ℓ), h τ) + doUncons l (RecMapPair x y) = (RecMapPair (lazyRemove l x) (lazyRemove l y), f (x .! l) (y .! l)) + \\ mapHas @f @ℓ @τ @ρ + \\ mapHas @g @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> (RMap h ρ, h τ) -> RMap h (Extend ℓ τ ρ) + doCons l (RMap r, h) = RMap (extend l h r) + \\ mapExtendSwap @h @ℓ @τ @ρ + +-- | A version of 'zipTransform' for when there is no constraint. +zipTransform' :: forall r (f :: * -> *) (g :: * -> *) (h :: * -> *) . + FreeForall r => (forall a. f a -> g a -> h a) -> Rec (Map f r) -> Rec (Map g r) -> Rec (Map h r) +zipTransform' = zipTransform @Unconstrained1 @r + +-- | Traverse a function over a record. Note that the fields of the record will +-- be accessed in lexicographic order by the labels. +traverse :: forall c f r. (Forall r c, Applicative f) => (forall a. c a => a -> f a) -> Rec r -> f (Rec r) +traverse f = sequence' @f @r @c . map @c @f @r f + +-- | Traverse a function over a Mapped record. Note that the fields of the record will +-- be accessed in lexicographic order by the labels. +traverseMap :: forall c (f :: * -> *) (g :: * -> *) (h :: * -> *) r. + (Forall r c, Applicative f) => (forall a. c a => g a -> f (h a)) -> Rec (Map g r) -> f (Rec (Map h r)) +traverseMap f = + sequence' @f @(Map h r) @(IsA c h) . + uncompose' @c @f @h @r . + transform @c @r @g @(Compose f h) (Compose . f) + \\ mapForall @h @r @c + +-- | A version of 'sequence' in which the constraint for 'Forall' can be chosen. +sequence' :: forall f r c. (Forall r c, Applicative f) + => Rec (Map f r) -> f (Rec r) +sequence' = getCompose . metamorph @_ @r @c @(,) @(RMap f) @(Compose f Rec) @f Proxy doNil doUncons doCons . RMap + where + doNil _ = Compose (pure empty) + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> RMap f ρ -> (RMap f (ρ .- ℓ), f τ) + doUncons l (RMap r) = first RMap $ lazyUncons l r + \\ mapHas @f @ℓ @τ @ρ + doCons l (Compose fr, fv) = Compose $ extend l <$> fv <*> fr + +-- | Applicative sequencing over a record. +sequence :: forall f r. (Applicative f, FreeForall r) + => Rec (Map f r) -> f (Rec r) +sequence = sequence' @_ @_ @Unconstrained1 + +-- | This function acts as the inversion of `sequence`, allowing one to move a +-- functor level into a record. +distribute :: forall f r. (FreeForall r, Functor f) => f (Rec r) -> Rec (Map f r) +distribute = unRMap . metamorph @_ @r @Unconstrained1 @(,) @(Compose f Rec) @(RMap f) @f Proxy doNil doUncons doCons . Compose + where + doNil _ = RMap empty + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, HasType ℓ τ ρ) + => Label ℓ -> Compose f Rec ρ -> (Compose f Rec (ρ .- ℓ), f τ) + doUncons l (Compose fr) = (Compose $ lazyRemove l <$> fr, (.! l) <$> fr) + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ) + => Label ℓ -> (RMap f ρ, f τ) -> RMap f (Extend ℓ τ ρ) + doCons l (RMap r, fv) = RMap (extend l fv r) + \\ mapExtendSwap @f @ℓ @τ @ρ + + +-- $compose +-- We can easily convert between mapping two functors over the types of a row +-- and mapping the composition of the two functors. The following two functions +-- perform this composition with the gaurantee that: +-- +-- >>> compose . uncompose = id +-- +-- >>> uncompose . compose = id + +-- | A version of 'compose' in which the constraint for 'Forall' can be chosen. +compose' :: forall c (f :: * -> *) (g :: * -> *) (r :: Row *) . Forall r c + => Rec (Map f (Map g r)) -> Rec (Map (Compose f g) r) +compose' = unRMap . metamorph @_ @r @c @(,) @(RMap2 f g) @(RMap (Compose f g)) @(Compose f g) Proxy doNil doUncons doCons . RMap2 + where + doNil _ = RMap empty + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> RMap2 f g ρ -> (RMap2 f g (ρ .- ℓ), Compose f g τ) + doUncons l (RMap2 r) = bimap RMap2 Compose $ lazyUncons l r + \\ mapHas @f @ℓ @(g τ) @(Map g ρ) + \\ mapHas @g @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> (RMap (Compose f g) ρ, Compose f g τ) -> RMap (Compose f g) (Extend ℓ τ ρ) + doCons l (RMap r, v) = RMap $ extend l v r + \\ mapExtendSwap @(Compose f g) @ℓ @τ @ρ + +-- | Convert from a record where two functors have been mapped over the types to +-- one where the composition of the two functors is mapped over the types. +compose :: forall (f :: * -> *) (g :: * -> *) r . FreeForall r + => Rec (Map f (Map g r)) -> Rec (Map (Compose f g) r) +compose = compose' @Unconstrained1 @f @g @r + +-- | A version of 'uncompose' in which the constraint for 'Forall' can be chosen. +uncompose' :: forall c (f :: * -> *) (g :: * -> *) r . Forall r c + => Rec (Map (Compose f g) r) -> Rec (Map f (Map g r)) +uncompose' = unRMap2 . metamorph @_ @r @c @(,) @(RMap (Compose f g)) @(RMap2 f g) @(Compose f g) Proxy doNil doUncons doCons . RMap + where + doNil _ = RMap2 empty + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> RMap (Compose f g) ρ -> (RMap (Compose f g) (ρ .- ℓ), Compose f g τ) + doUncons l (RMap r) = first RMap $ lazyUncons l r + \\ mapHas @(Compose f g) @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> (RMap2 f g ρ, Compose f g τ) -> RMap2 f g (Extend ℓ τ ρ) + doCons l (RMap2 r, Compose v) = RMap2 $ extend l v r + \\ mapExtendSwap @f @ℓ @(g τ) @(Map g ρ) + \\ mapExtendSwap @g @ℓ @τ @ρ + +-- | Convert from a record where the composition of two functors have been mapped +-- over the types to one where the two functors are mapped individually one at a +-- time over the types. +uncompose :: forall (f :: * -> *) (g :: * -> *) r . FreeForall r + => Rec (Map (Compose f g) r) -> Rec (Map f (Map g r)) +uncompose = uncompose' @Unconstrained1 @f @g @r + + +-- | Coerce a record to a coercible representation. The 'BiForall' in the context +-- indicates that the type of every field in @r1@ can be coerced to the type of +-- the corresponding fields in @r2@. +-- +-- Internally, this is implemented just with `unsafeCoerce`, but we provide the +-- following implementation as a proof: +-- +-- > newtype ConstR a b = ConstR (Rec a) +-- > newtype FlipConstR a b = FlipConstR { unFlipConstR :: Rec b } +-- > coerceRec :: forall r1 r2. BiForall r1 r2 Coercible => Rec r1 -> Rec r2 +-- > coerceRec = unFlipConstR . biMetamorph @_ @_ @r1 @r2 @Coercible @(,) @ConstR @FlipConstR @Const Proxy doNil doUncons doCons . ConstR +-- > where +-- > doNil _ = FlipConstR empty +-- > doUncons l (ConstR r) = bimap ConstR Const $ lazyUncons l r +-- > doCons :: forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, Coercible τ1 τ2) +-- > => Label ℓ -> (FlipConstR ρ1 ρ2, Const τ1 τ2) -> FlipConstR (Extend ℓ τ1 ρ1) (Extend ℓ τ2 ρ2) +-- > doCons l (FlipConstR r, Const v) = FlipConstR $ extend l (coerce @τ1 @τ2 v) r +coerceRec :: forall r1 r2. BiForall r1 r2 Coercible => Rec r1 -> Rec r2 +coerceRec = unsafeCoerce + + +-- | RZipPair is used internally as a type level lambda for zipping records. +newtype RecPair (ρ1 :: Row *) (ρ2 :: Row *) = RecPair (Rec ρ1, Rec ρ2) +newtype RZipPair (ρ1 :: Row *) (ρ2 :: Row *) = RZipPair { unRZipPair :: Rec (Zip ρ1 ρ2) } + +-- | Zips together two records that have the same set of labels. +zip :: forall r1 r2. FreeBiForall r1 r2 => Rec r1 -> Rec r2 -> Rec (Zip r1 r2) +zip r1 r2 = unRZipPair $ biMetamorph @_ @_ @r1 @r2 @Unconstrained2 @(,) @RecPair @RZipPair @(,) Proxy doNil doUncons doCons $ RecPair (r1, r2) + where + doNil _ = RZipPair empty + doUncons l (RecPair (r1, r2)) = (RecPair (lazyRemove l r1, lazyRemove l r2), (r1 .! l, r2 .! l)) + doCons :: forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ) + => Label ℓ -> (RZipPair ρ1 ρ2, (τ1, τ2)) -> RZipPair (Extend ℓ τ1 ρ1) (Extend ℓ τ2 ρ2) + doCons l (RZipPair r, vs) = RZipPair $ extend l vs r + \\ zipExtendSwap @ℓ @τ1 @ρ1 @τ2 @ρ2 + +{-------------------------------------------------------------------- + Record initialization +--------------------------------------------------------------------} + +-- | Initialize a record with a default value at each label. +default' :: forall c ρ. (Forall ρ c, AllUniqueLabels ρ) => (forall a. c a => a) -> Rec ρ +default' v = runIdentity $ defaultA @c $ pure v + +-- | Initialize a record with a default value at each label; works over an 'Applicative'. +defaultA :: forall c f ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) + => (forall a. c a => f a) -> f (Rec ρ) +defaultA v = fromLabelsA @c $ pure v + +-- | Initialize a record, where each value is determined by the given function over +-- the label at that value. +fromLabels :: forall c ρ. (Forall ρ c, AllUniqueLabels ρ) + => (forall l a. (KnownSymbol l, c a) => Label l -> a) -> Rec ρ +fromLabels f = runIdentity $ fromLabelsA @c $ (pure .) f + +-- | Initialize a record, where each value is determined by the given function over +-- the label at that value. This function works over an 'Applicative'. +fromLabelsA :: forall c f ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) + => (forall l a. (KnownSymbol l, c a) => Label l -> f a) -> f (Rec ρ) +fromLabelsA mk = getCompose $ metamorph @_ @ρ @c @Const @(Const ()) @(Compose f Rec) @Proxy Proxy doNil doUncons doCons (Const ()) + where doNil _ = Compose $ pure empty + doUncons _ _ = Const $ Const () + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> Const (Compose f Rec ρ) (Proxy τ) -> Compose f Rec (Extend ℓ τ ρ) + doCons l (Const (Compose r)) = Compose $ extend l <$> mk @ℓ @τ l <*> r + +-- | Initialize a record over a `Map`. +fromLabelsMapA :: forall c f g ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) + => (forall l a. (KnownSymbol l, c a) => Label l -> f (g a)) -> f (Rec (Map g ρ)) +fromLabelsMapA f = fromLabelsA @(IsA c g) @f @(Map g ρ) inner + \\ mapForall @g @ρ @c + \\ uniqueMap @g @ρ + where inner :: forall l a. (KnownSymbol l, IsA c g a) => Label l -> f a + inner l = case as @c @g @a of As -> f l + + +{-------------------------------------------------------------------- + Dynamic compatibility +--------------------------------------------------------------------} + +-- | Converts a 'Rec' into a 'HashMap' of 'Dynamic's. +toDynamicMap :: Forall r Typeable => Rec r -> HashMap Text Dynamic +toDynamicMap = eraseToHashMap @Typeable @_ @Text @Dynamic toDyn + +-- | Produces a 'Rec' from a 'HashMap' of 'Dynamic's. +fromDynamicMap :: (AllUniqueLabels r, Forall r Typeable) + => HashMap Text Dynamic -> Maybe (Rec r) +fromDynamicMap m = fromLabelsA @Typeable + $ \ (toKey -> k) -> M.lookup k m >>= fromDynamic + + +{-------------------------------------------------------------------- + Generic instance +--------------------------------------------------------------------} + +-- The generic structure we want Recs to have is not the hidden internal one, +-- but rather one that appears as a Haskell record. Thus, we can't derive +-- Generic automatically. +-- +-- The following Generic instance creates a representation of a Rec that is +-- very similar to a native Haskell record except that the tree of pairs (':*:') +-- that it produces will be extremely unbalanced. I don't think this is a problem. +-- Furthermore, because we don't want Recs to always have a trailing unit on +-- the end, we must have a special case for singleton Recs, which means that +-- we can't use metamorph. + +instance GenericRec r => G.Generic (Rec r) where + type Rep (Rec r) = + G.D1 ('G.MetaData "Rec" "Data.Row.Records" "row-types" 'False) + (G.C1 ('G.MetaCons "Rec" 'G.PrefixI 'True) + (RepRec r)) + from = G.M1 . G.M1 . fromRec + to = toRec . G.unM1 . G.unM1 + +class GenericRec r where + type RepRec (r :: Row *) :: * -> * + fromRec :: Rec r -> RepRec r x + toRec :: RepRec r x -> Rec r + +instance GenericRec Empty where + type RepRec (R '[]) = G.U1 + fromRec _ = G.U1 + toRec _ = empty + +instance KnownSymbol name => GenericRec (R '[name :-> t]) where + type RepRec (R (name :-> t ': '[])) = G.S1 + ('G.MetaSel ('Just name) 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) + (G.Rec0 t) + fromRec (_ :== a) = G.M1 (G.K1 a) + toRec (G.M1 (G.K1 a)) = (Label @name) :== a + +instance + ( r ~ (name' :-> t' ': r'), GenericRec (R r) + , KnownSymbol name, Extend name t ('R r) ≈ 'R (name :-> t ': r) + ) => GenericRec (R (name :-> t ': (name' :-> t' ': r'))) where + type RepRec (R (name :-> t ': (name' :-> t' ': r'))) = (G.S1 + ('G.MetaSel ('Just name) 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) + (G.Rec0 t)) G.:*: RepRec (R (name' :-> t' ': r')) + fromRec r = G.M1 (G.K1 (r .! Label @name)) G.:*: fromRec (lazyRemove @name Label r) + toRec (G.M1 (G.K1 a) G.:*: r) = extend @_ @name @('R (name' :-> t' ': r')) Label a (toRec r) + +{-------------------------------------------------------------------- + Native data type compatibility +--------------------------------------------------------------------} +-- ToNative is shamelessly copied from +-- https://www.athiemann.net/2017/07/02/superrecord.html + +-- $native +-- The 'toNative' and 'fromNative' functions allow one to convert between +-- 'Rec's and regular Haskell data types ("native" types) that have a single constructor and any +-- number of named fields with the same names and types as the 'Rec'. As expected, +-- they compose to form the identity. Alternatively, one may use 'toNativeGeneral', +-- which allows fields to be dropped when a record has excess fields compared +-- to the native type. Because of this, 'toNativeGeneral' requires a type +-- application (although 'fromNative' does not). The only requirement is that +-- the native Haskell data type be an instance of 'Generic'. +-- +-- For example, consider the following simple data type: +-- +-- >>> data Person = Person { name :: String, age :: Int} deriving (Generic, Show) +-- +-- Then, we have the following: +-- +-- >>> toNative @Person $ #name .== "Alice" .+ #age .== 7 .+ #hasDog .== True +-- Person {name = "Alice", age = 7} +-- >>> fromNative $ Person "Bob" 9 +-- { age=9, name="Bob" } + +type family NativeRow t where + NativeRow t = NativeRowG (G.Rep t) + +type family NativeRowG t where + NativeRowG (G.M1 G.D m cs) = NativeRowG cs + NativeRowG (G.M1 G.C m cs) = NativeRowG cs + NativeRowG G.U1 = Empty + NativeRowG (l G.:*: r) = NativeRowG l .+ NativeRowG r + NativeRowG (G.M1 G.S ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) = name .== t + + +-- | Conversion helper to turn a Haskell record into a row-types extensible +-- record. Note that the native Haskell type must be an instance of 'Generic'. +class FromNativeG a where + fromNative' :: a x -> Rec (NativeRowG a) + +instance FromNativeG cs => FromNativeG (G.D1 m cs) where + fromNative' (G.M1 xs) = fromNative' xs + +instance FromNativeG cs => FromNativeG (G.C1 m cs) where + fromNative' (G.M1 xs) = fromNative' xs + +instance FromNativeG G.U1 where + fromNative' G.U1 = empty + +instance KnownSymbol name => FromNativeG (G.S1 ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) where + fromNative' (G.M1 (G.K1 x)) = (Label @name) .== x + +instance (FromNativeG l, FromNativeG r, FreeForall (NativeRowG l)) => FromNativeG (l G.:*: r) where + fromNative' (x G.:*: y) = fromNative' @l x .+ fromNative' @r y + +type FromNative t = (G.Generic t, FromNativeG (G.Rep t)) + +-- | Convert a Haskell record to a row-types Rec. +fromNative :: FromNative t => t -> Rec (NativeRow t) +fromNative = fromNative' . G.from + + +-- | Conversion helper to bring a record back into a Haskell type. Note that the +-- native Haskell type must be an instance of 'Generic'. +class ToNativeG a where + toNative' :: Rec (NativeRowG a) -> a x + +instance ToNativeG cs => ToNativeG (G.D1 m cs) where + toNative' xs = G.M1 $ toNative' xs + +instance ToNativeG cs => ToNativeG (G.C1 m cs) where + toNative' xs = G.M1 $ toNative' xs + +instance ToNativeG G.U1 where + toNative' _ = G.U1 + +instance (KnownSymbol name) => ToNativeG (G.S1 ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) where + toNative' r = G.M1 $ G.K1 $ r .! (Label @name) + +instance (ToNativeG l, ToNativeG r, Disjoint (NativeRowG l) (NativeRowG r)) + => ToNativeG (l G.:*: r) where + toNative' r = toNative' r1 G.:*: toNative' r2 + where + (r1 :: Rec (NativeRowG l)) :+ (r2 :: Rec (NativeRowG r)) = r + +type ToNative t = (G.Generic t, ToNativeG (G.Rep t)) + +-- | Convert a record to an exactly matching native Haskell type. +toNative :: ToNative t => Rec (NativeRow t) -> t +toNative = G.to . toNative' + + + +-- | Conversion helper to bring a record back into a Haskell type. Note that the +-- native Haskell type must be an instance of 'Generic'. +class ToNativeGeneralG a ρ where + toNativeGeneral' :: Rec ρ -> a x + +instance ToNativeGeneralG cs ρ => ToNativeGeneralG (G.D1 m cs) ρ where + toNativeGeneral' xs = G.M1 $ toNativeGeneral' xs + +instance ToNativeGeneralG cs ρ => ToNativeGeneralG (G.C1 m cs) ρ where + toNativeGeneral' xs = G.M1 $ toNativeGeneral' xs + +instance ToNativeGeneralG G.U1 ρ where + toNativeGeneral' _ = G.U1 + +instance (KnownSymbol name, ρ .! name ≈ t) + => ToNativeGeneralG (G.S1 ('G.MetaSel ('Just name) p s l) (G.Rec0 t)) ρ where + toNativeGeneral' r = G.M1 $ G.K1 $ r .! (Label @name) + +instance (ToNativeGeneralG l ρ, ToNativeGeneralG r ρ) + => ToNativeGeneralG (l G.:*: r) ρ where + toNativeGeneral' r = toNativeGeneral' r G.:*: toNativeGeneral' r + +type ToNativeGeneral t ρ = (G.Generic t, ToNativeGeneralG (G.Rep t) ρ) + +-- | Convert a record to a native Haskell type. +toNativeGeneral :: ToNativeGeneral t ρ => Rec ρ -> t +toNativeGeneral = G.to . toNativeGeneral' + + +{-------------------------------------------------------------------- + Generic-lens compatibility +--------------------------------------------------------------------} + +-- | Every field in a row-types based record has a 'HasField' instance. +instance {-# OVERLAPPING #-} + ( KnownSymbol name + , r' .! name ≈ b + , r .! name ≈ a + , r' ~ Modify name b r + , r ~ Modify name a r') + => HasField name (Rec r) (Rec r') a b where + field = focus (Label @name) + {-# INLINE field #-} + +instance {-# OVERLAPPING #-} + ( KnownSymbol name + , r .! name ≈ a + , r ~ Modify name a r) + => HasField' name (Rec r) a where + field' = focus (Label @name) + {-# INLINE field' #-}
+ src/Data/Row/Switch.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +----------------------------------------------------------------------------- +-- | +-- Module: Data.Row.Switch +-- +-- This module provides the ability to discharge a polymorphic variant using +-- a record that has matching fields. +-- +----------------------------------------------------------------------------- + + +module Data.Row.Switch + ( AppliesTo(..) + , switch + , caseon + ) +where + +import Data.Proxy + +import Data.Bifunctor (Bifunctor(..)) +import Data.Row.Internal +import Data.Row.Records +import Data.Row.Variants + +-- | A simple class that we use to provide a constraint for function application. +class AppliesTo r f x | r x -> f, f r -> x where + applyTo :: f -> x -> r +instance AppliesTo r (x -> r) x where + applyTo = ($) + +-- | A pair of a record and a variant. +data SwitchData r v = SwitchData (Rec r) (Var v) + +-- | Like 'Const' but for two ignored type arguments. +newtype Const2 x y z = Const2 { getConst2 :: x } + +-- | A 'Var' and a 'Rec' can combine if their rows line up properly. +-- Given a Variant along with a Record of functions from each possible value +-- of the variant to a single output type, apply the correct +-- function to the value in the variant. +switch :: forall v r x. BiForall r v (AppliesTo x) => Var v -> Rec r -> x +switch v r = getConst2 $ biMetamorph @_ @_ @r @v @(AppliesTo x) @Either @SwitchData @(Const2 x) @(Const2 x) + Proxy doNil doUncons doCons $ SwitchData r v + where + doNil (SwitchData _ v) = impossible v + doUncons :: forall ℓ f τ ϕ ρ. (KnownSymbol ℓ, AppliesTo x f τ, HasType ℓ f ϕ, HasType ℓ τ ρ) + => Label ℓ -> SwitchData ϕ ρ -> Either (SwitchData (ϕ .- ℓ) (ρ .- ℓ)) (Const2 x f τ) + doUncons l (SwitchData r v) = bimap (SwitchData $ lazyRemove l r) (Const2 . applyTo (r .! l)) $ trial v l + -- doCons :: forall ℓ f τ ϕ ρ. (KnownSymbol ℓ, AppliesTo x f τ) + -- => Label ℓ -> Either (Const2 x f τ) (Const2 x ϕ ρ) -> Const2 x (Extend ℓ f ϕ) (Extend ℓ τ ρ) + doCons _ (Left (Const2 x)) = Const2 x + doCons _ (Right (Const2 x)) = Const2 x + +-- | The same as 'switch' but with the argument order reversed +caseon :: forall v r x. BiForall r v (AppliesTo x) => Rec r -> Var v -> x +caseon = flip switch
+ src/Data/Row/Variants.hs view
@@ -0,0 +1,761 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE EmptyCase #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE UndecidableInstances #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row.Variants +-- +-- This module implements extensible variants using closed type families. +-- +----------------------------------------------------------------------------- + + +module Data.Row.Variants + ( + -- * Types and constraints + Label(..) + , KnownSymbol, AllUniqueLabels, WellBehaved + , Var, Row, Empty, type (≈) + -- * Construction + , HasType, pattern IsJust, singleton, unSingleton + , fromLabels, fromLabelsMap + -- ** Extension + , type (.\), Lacks, type (.\/), diversify, extend, type (.+) + -- ** Modification + , update, focus, Modify, rename, Rename + -- * Destruction + , impossible, trial, trial', multiTrial, view + , restrict, split + -- ** Types for destruction + , type (.!), type (.-), type (.\\), type (.==) + -- * Native Conversion + -- $native + , toNative, fromNative, fromNativeGeneral + , ToNative, FromNative, FromNativeGeneral + , NativeRow + -- * Row operations + -- ** Map + , Map, map, map', transform, transform' + -- ** Fold + , Forall, erase, eraseWithLabels, eraseZipGeneral, eraseZip + -- ** Applicative-like functions + , traverse, traverseMap + , sequence + -- ** Compose + -- $compose + , compose, uncompose + -- ** labels + , labels + -- ** ApSingle functions + , eraseSingle, mapSingle, eraseZipSingle + -- ** Coerce + , coerceVar + ) +where + +import Prelude hiding (map, sequence, traverse, zip) + +import Control.Applicative +import Control.DeepSeq (NFData(..), deepseq) + +import Data.Bifunctor (Bifunctor(..)) +import Data.Coerce +import Data.Constraint (Constraint) +import Data.Functor.Compose +import Data.Functor.Identity +import Data.Functor.Product +import Data.Generics.Sum.Constructors (AsConstructor(..), AsConstructor'(..)) +import Data.Maybe (fromMaybe) +import Data.Profunctor (Choice(..), Profunctor(..)) +import Data.Proxy +import Data.String (IsString) +import Data.Text (Text) + +import qualified GHC.Generics as G +import GHC.TypeLits + +import Unsafe.Coerce + +import Data.Row.Dictionaries +import Data.Row.Internal + +{-------------------------------------------------------------------- + Polymorphic Variants +--------------------------------------------------------------------} + +-- | The variant type. +data Var (r :: Row *) where + OneOf :: Text -> HideType -> Var r + +instance Forall r Show => Show (Var r) where + show v = (\ (x, y) -> "{" ++ x ++ "=" ++ y ++ "}") $ eraseWithLabels @Show show v + +instance Forall r Eq => Eq (Var r) where + r == r' = fromMaybe False $ eraseZip @Eq (==) r r' + +instance (Forall r Eq, Forall r Ord) => Ord (Var r) where + compare :: Var r -> Var r -> Ordering + compare = eraseZipGeneral @Ord @r @Ordering @Text $ \case + (Left (_, x, y)) -> compare x y + (Right ((s1, _), (s2, _))) -> compare s1 s2 + +instance Forall r NFData => NFData (Var r) where + rnf r = getConst $ metamorph @_ @r @NFData @Either @Var @(Const ()) @Identity Proxy empty doUncons doCons r + where empty = const $ Const () + doUncons l = second Identity . flip trial l + doCons _ x = deepseq x $ Const () + + +{-------------------------------------------------------------------- + Basic Operations +--------------------------------------------------------------------} + +-- | A Variant with no options is uninhabited. +impossible :: Var Empty -> a +impossible _ = error "Impossible! Somehow, a variant of nothing was produced." + +-- | A quick constructor to create a singleton variant. +singleton :: KnownSymbol l => Label l -> a -> Var (l .== a) +singleton = IsJust + +-- | A quick destructor for singleton variants. +unSingleton :: forall l a. KnownSymbol l => Var (l .== a) -> (Label l, a) +unSingleton (OneOf _ (HideType x)) = (l, unsafeCoerce x) where l = Label @l + +-- | A pattern for variants; can be used to both destruct a variant +-- when in a pattern position or construct one in an expression position. +pattern IsJust :: forall l r. (AllUniqueLabels r, KnownSymbol l) => Label l -> r .! l -> Var r +pattern IsJust l a <- (isJustHelper @l -> (l, Just a)) where + IsJust (toKey -> l) = OneOf l . HideType + +isJustHelper :: forall l r. KnownSymbol l => Var r -> (Label l, Maybe (r .! l)) +isJustHelper v = (l, view l v) where l = Label @l + +-- | Make the variant arbitrarily more diverse. +diversify :: forall r' r. Var r -> Var (r .\/ r') +diversify = unsafeCoerce -- (OneOf l x) = OneOf l x + +-- | A weaker version of 'diversify', but it's helpful for 'metamorph' as it explicitly +-- uses 'Extend'. +extend :: forall a l r. KnownSymbol l => Label l -> Var r -> Var (Extend l a r) +extend _ = unsafeCoerce + +-- | If the variant exists at the given label, update it to the given value. +-- Otherwise, do nothing. +update :: (KnownSymbol l, r .! l ≈ a) => Label l -> a -> Var r -> Var r +update (toKey -> l') a (OneOf l x) = OneOf l $ if l == l' then HideType a else x + +-- | If the variant exists at the given label, focus on the value associated with it. +-- Otherwise, do nothing. +focus :: forall l r r' a b p f. + ( AllUniqueLabels r + , AllUniqueLabels r' + , KnownSymbol l + , r .! l ≈ a + , r' .! l ≈ b + , r' ≈ (r .- l) .\/ (l .== b) + , Applicative f + , Choice p + ) => Label l -> p a (f b) -> p (Var r) (f (Var r')) +focus (toKey -> l) = + dimap unwrap rewrap . left' + where + unwrap :: Var r -> Either a (Var r') + unwrap (OneOf l' (HideType x)) + | l == l' = Left (unsafeCoerce x) + | otherwise = Right (OneOf l' (HideType x)) + rewrap :: Either (f b) (Var r') -> f (Var r') + rewrap = either (fmap $ OneOf l . HideType) pure + +-- | Rename the given label. +rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Var r -> Var (Rename l l' r) +rename (toKey -> l1) (toKey -> l2) (OneOf l x) = OneOf (if l == l1 then l2 else l) x + +-- | Convert a variant into either the value at the given label or a variant without +-- that label. This is the basic variant destructor. +trial :: KnownSymbol l => Var r -> Label l -> Either (Var (r .- l)) (r .! l) +trial (OneOf l (HideType x)) (toKey -> l') = if l == l' then Right (unsafeCoerce x) else Left (OneOf l (HideType x)) + +-- | A version of 'trial' that ignores the leftover variant. +trial' :: KnownSymbol l => Var r -> Label l -> Maybe (r .! l) +trial' = (either (const Nothing) Just .) . trial + +-- | A trial over multiple types +multiTrial :: forall x y. (AllUniqueLabels x, Forall (y .\\ x) Unconstrained1) => Var y -> Either (Var (y .\\ x)) (Var x) +multiTrial (OneOf l x) = if l `elem` labels @(y .\\ x) @Unconstrained1 then Left (OneOf l x) else Right (OneOf l x) + +-- | A convenient function for using view patterns when dispatching variants. +-- For example: +-- +-- @ +-- myShow :: Var ("y" '::= String :| "x" '::= Int :| Empty) -> String +-- myShow (view x -> Just n) = "Int of "++show n +-- myShow (view y -> Just s) = "String of "++s @ +view :: KnownSymbol l => Label l -> Var r -> Maybe (r .! l) +view = flip trial' + +-- | Split a variant into two sub-variants. +split :: forall s r. (WellBehaved s, Subset s r) => Var r -> Either (Var (r .\\ s)) (Var s) +split (OneOf l a) | l `elem` labels @s @Unconstrained1 = Right $ OneOf l a + | otherwise = Left $ OneOf l a + +-- | Arbitrary variant restriction. Turn a variant into a subset of itself. +restrict :: forall r r'. (WellBehaved r, Subset r r') => Var r' -> Maybe (Var r) +restrict = either (pure Nothing) Just . split + + +{-------------------------------------------------------------------- + Folds and maps +--------------------------------------------------------------------} + +-- | A standard fold +erase :: forall c ρ b. Forall ρ c => (forall a. c a => a -> b) -> Var ρ -> b +erase f = snd @String . eraseWithLabels @c f + +-- | A fold with labels +eraseWithLabels :: forall c ρ s b. (Forall ρ c, IsString s) => (forall a. c a => a -> b) -> Var ρ -> (s,b) +eraseWithLabels f = getConst . metamorph @_ @ρ @c @Either @Var @(Const (s,b)) @Identity Proxy impossible doUncons doCons + where doUncons l = second Identity . flip trial l + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ) + => Label ℓ -> Either (Const (s,b) ρ) (Identity τ) -> Const (s,b) (Extend ℓ τ ρ) + doCons _ (Left (Const c)) = Const c + doCons l (Right (Identity x)) = Const (show' l, f x) + + +data ErasedVal c s = forall y. c y => ErasedVal (s, y) +data ErasePair c s ρ = ErasePair (Either (ErasedVal c s) (Var ρ)) (Either (ErasedVal c s) (Var ρ)) + +-- | A fold over two variants at once. A call @eraseZipGeneral f x y@ will return +-- @f (Left (show l, a, b))@ when 'x' and 'y' both have values at the same label 'l' +-- and will return @f (Right ((show l1, a), (show l2, b)))@ when they have values +-- at different labels 'l1' and 'l2' respectively. +eraseZipGeneral + :: forall c ρ b s. (Forall ρ c, IsString s) + => (forall x y. (c x, c y) => Either (s, x, x) ((s, x), (s, y)) -> b) + -> Var ρ -> Var ρ -> b +eraseZipGeneral f x y = getConst $ metamorph @_ @ρ @c @Either @(ErasePair c s) @(Const b) @(Const b) Proxy doNil doUncons doCons (ErasePair (Right x) (Right y)) + where + doNil (ErasePair (Left (ErasedVal a)) (Left (ErasedVal b))) = + Const $ f $ Right (a, b) + doNil (ErasePair (Right x) _) = impossible x + doNil (ErasePair _ (Right y)) = impossible y + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> ErasePair c s ρ -> Either (ErasePair c s (ρ .- ℓ)) (Const b τ) + doUncons _ (ErasePair (Left (ErasedVal a)) (Left (ErasedVal b))) = + Right $ Const $ f $ Right (a, b) + doUncons l (ErasePair (Right x) (Left eb)) = case (trial x l, eb) of + (Right a, ErasedVal b) -> Right $ Const $ f $ Right ((show' l, a), b) + (Left x', _) -> Left $ ErasePair (Right x') (Left eb) + doUncons l (ErasePair (Left ea) (Right y)) = case (ea, trial y l) of + (ErasedVal a, Right b) -> Right $ Const $ f $ Right (a, (show' l, b)) + (_, Left x') -> Left $ ErasePair (Left ea) (Right x') + doUncons l (ErasePair (Right x) (Right y)) = case (trial x l, trial y l) of + (Right (a :: x), Right b) -> Right $ Const $ f @x @x $ Left (show' l, a, b) + (Right a, Left y') -> Left $ ErasePair (Left $ ErasedVal (show' l, a)) (Right y') + (Left x', Right b) -> Left $ ErasePair (Right x') (Left $ ErasedVal (show' l, b)) + (Left x', Left y') -> Left $ ErasePair (Right x') (Right y') + doCons _ (Left (Const b)) = Const b + doCons _ (Right (Const b)) = Const b + + +-- | A simpler fold over two variants at once +eraseZip :: forall c ρ b. Forall ρ c => (forall a. c a => a -> a -> b) -> Var ρ -> Var ρ -> Maybe b +eraseZip f = eraseZipGeneral @c @ρ @(Maybe b) @Text $ \case + Left (_,x,y) -> Just (f x y) + _ -> Nothing + + +-- | VMap is used internally as a type level lambda for defining variant maps. +newtype VMap (f :: * -> *) (ρ :: Row *) = VMap { unVMap :: Var (Map f ρ) } +newtype VMap2 (f :: * -> *) (g :: * -> *) (ρ :: Row *) = VMap2 { unVMap2 :: Var (Map f (Map g ρ)) } + +-- | A function to map over a variant given a constraint. +map :: forall c f r. Forall r c => (forall a. c a => a -> f a) -> Var r -> Var (Map f r) +map f = unVMap . metamorph @_ @r @c @Either @Var @(VMap f) @Identity Proxy impossible doUncons doCons + where + doUncons l = second Identity . flip trial l + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Either (VMap f ρ) (Identity τ) -> VMap f (Extend ℓ τ ρ) + doCons l (Left (VMap v)) = VMap $ extend @(f τ) l v + \\ mapExtendSwap @f @ℓ @τ @ρ + doCons l (Right (Identity x)) = VMap $ IsJust l (f x) + \\ mapExtendSwap @f @ℓ @τ @ρ + \\ extendHas @ℓ @(f τ) @(Map f ρ) + \\ uniqueMap @f @(Extend ℓ τ ρ) + +-- | A function to map over a variant given no constraint. +map' :: forall f r. FreeForall r => (forall a. a -> f a) -> Var r -> Var (Map f r) +map' = map @Unconstrained1 + +-- | Lifts a natrual transformation over a variant. In other words, it acts as a +-- variant transformer to convert a variant of @f a@ values to a variant of @g a@ +-- values. If no constraint is needed, instantiate the first type argument with +-- 'Unconstrained1'. +transform :: forall c r (f :: * -> *) (g :: * -> *). Forall r c => (forall a. c a => f a -> g a) -> Var (Map f r) -> Var (Map g r) +transform f = unVMap . metamorph @_ @r @c @Either @(VMap f) @(VMap g) @f Proxy doNil doUncons doCons . VMap + where + doNil = impossible . unVMap + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, HasType ℓ τ ρ) + => Label ℓ -> VMap f ρ -> Either (VMap f (ρ .- ℓ)) (f τ) + doUncons l = first VMap . flip trial l . unVMap + \\ mapHas @f @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Either (VMap g ρ) (f τ) -> VMap g (Extend ℓ τ ρ) + doCons l (Left (VMap v)) = VMap $ extend @(g τ) l v + \\ mapExtendSwap @g @ℓ @τ @ρ + doCons l (Right x) = VMap $ IsJust l (f x) + \\ mapExtendSwap @g @ℓ @τ @ρ + \\ extendHas @ℓ @(g τ) @(Map g ρ) + \\ uniqueMap @g @(Extend ℓ τ ρ) + +-- | A form of @transformC@ that doesn't have a constraint on @a@ +transform' :: forall r (f :: * -> *) (g :: * -> *) . FreeForall r => (forall a. f a -> g a) -> Var (Map f r) -> Var (Map g r) +transform' = transform @Unconstrained1 @r + +-- | Traverse a function over a variant. +traverse :: forall c f r. (Forall r c, Applicative f) => (forall a. c a => a -> f a) -> Var r -> f (Var r) +traverse f = sequence' @f @r @c . map @c @f @r f + +-- | Traverse a function over a Mapped variant. +traverseMap :: forall c (f :: * -> *) (g :: * -> *) (h :: * -> *) r. + (Forall r c, Applicative f) => (forall a. c a => g a -> f (h a)) -> Var (Map g r) -> f (Var (Map h r)) +traverseMap f = + sequence' @f @(Map h r) @(IsA c h) . + uncompose' @c @f @h @r . + transform @c @r @g @(Compose f h) (Compose . f) + \\ mapForall @h @r @c + +-- | Applicative sequencing over a variant with arbitrary constraint. +sequence' :: forall f r c. (Forall r c, Applicative f) => Var (Map f r) -> f (Var r) +sequence' = getCompose . metamorph @_ @r @c @Either @(VMap f) @(Compose f Var) @f Proxy doNil doUncons doCons . VMap + where + doNil = impossible . unVMap + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, HasType ℓ τ ρ) + => Label ℓ -> VMap f ρ -> Either (VMap f (ρ .- ℓ)) (f τ) + doUncons l = first VMap . flip trial l . unVMap + \\ mapHas @f @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Either (Compose f Var ρ) (f τ) -> Compose f Var (Extend ℓ τ ρ) + doCons l (Left (Compose v)) = Compose $ extend @τ l <$> v + doCons l (Right fx) = Compose $ IsJust l <$> fx + \\ extendHas @ℓ @τ @ρ + + +-- | Applicative sequencing over a variant +sequence :: forall f r. (FreeForall r, Applicative f) => Var (Map f r) -> f (Var r) +sequence = sequence' @f @r @Unconstrained1 + +-- $compose +-- We can easily convert between mapping two functors over the types of a row +-- and mapping the composition of the two functors. The following two functions +-- perform this composition with the gaurantee that: +-- +-- >>> compose . uncompose = id +-- +-- >>> uncompose . compose = id + +-- | Convert from a variant where two functors have been mapped over the types to +-- one where the composition of the two functors is mapped over the types. +compose :: forall (f :: * -> *) (g :: * -> *) r . FreeForall r => Var (Map f (Map g r)) -> Var (Map (Compose f g) r) +compose = unVMap . metamorph @_ @r @Unconstrained1 @Either @(VMap2 f g) @(VMap (Compose f g)) @(Compose f g) Proxy doNil doUncons doCons . VMap2 + where + doNil = impossible . unVMap2 + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, HasType ℓ τ ρ) + => Label ℓ -> VMap2 f g ρ -> Either (VMap2 f g (ρ .- ℓ)) (Compose f g τ) + doUncons l = bimap VMap2 Compose . flip trial l . unVMap2 + \\ mapHas @f @ℓ @(g τ) @(Map g ρ) + \\ mapHas @g @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Either (VMap (Compose f g) ρ) (Compose f g τ) -> VMap (Compose f g) (Extend ℓ τ ρ) + doCons l (Left (VMap v)) = VMap $ extend @(Compose f g τ) l v + \\ mapExtendSwap @(Compose f g) @ℓ @τ @ρ + doCons l (Right x) = VMap $ IsJust l x + \\ mapExtendSwap @(Compose f g) @ℓ @τ @ρ + \\ extendHas @ℓ @((Compose f g) τ) @(Map (Compose f g) ρ) + \\ uniqueMap @(Compose f g) @(Extend ℓ τ ρ) + +-- | A version of 'uncompose' that allows an arbitrary constraint. +uncompose' :: forall c (f :: * -> *) (g :: * -> *) r . Forall r c => Var (Map (Compose f g) r) -> Var (Map f (Map g r)) +uncompose' = unVMap2 . metamorph @_ @r @c @Either @(VMap (Compose f g)) @(VMap2 f g) @(Compose f g) Proxy doNil doUncons doCons . VMap + where + doNil = impossible . unVMap + doUncons :: forall ℓ τ ρ. (KnownSymbol ℓ, HasType ℓ τ ρ) + => Label ℓ -> VMap (Compose f g) ρ -> Either (VMap (Compose f g) (ρ .- ℓ)) (Compose f g τ) + doUncons l = first VMap . flip trial l . unVMap + \\ mapHas @(Compose f g) @ℓ @τ @ρ + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Either (VMap2 f g ρ) (Compose f g τ) -> VMap2 f g (Extend ℓ τ ρ) + doCons l (Left (VMap2 v)) = VMap2 $ extend @(f (g τ)) l v + \\ mapExtendSwap @f @ℓ @(g τ) @(Map g ρ) + \\ mapExtendSwap @g @ℓ @τ @ρ + doCons l (Right (Compose x)) = VMap2 $ IsJust l x + \\ mapExtendSwap @f @ℓ @(g τ) @(Map g ρ) + \\ mapExtendSwap @g @ℓ @τ @ρ + \\ extendHas @ℓ @(f (g τ)) @(Map f (Map g ρ)) + \\ uniqueMap @f @(Extend ℓ (g τ) (Map g ρ)) + \\ uniqueMap @g @(Extend ℓ τ ρ) + +-- | Convert from a variant where the composition of two functors have been mapped +-- over the types to one where the two functors are mapped individually one at a +-- time over the types. +uncompose :: forall (f :: * -> *) (g :: * -> *) r . FreeForall r => Var (Map (Compose f g) r) -> Var (Map f (Map g r)) +uncompose = uncompose' @Unconstrained1 @f @g @r + +-- | Coerce a variant to a coercible representation. The 'BiForall' in the context +-- indicates that the type of any option in @r1@ can be coerced to the type of +-- the corresponding option in @r2@. +-- +-- Internally, this is implemented just with `unsafeCoerce`, but we provide the +-- following implementation as a proof: +-- +-- > newtype ConstV a b = ConstV { unConstV :: Var a } +-- > newtype ConstV a b = FlipConstV { unFlipConstV :: Var b } +-- > coerceVar :: forall r1 r2. BiForall r1 r2 Coercible => Var r1 -> Var r2 +-- > coerceVar = unFlipConstV . biMetamorph @_ @_ @r1 @r2 @Coercible @Either @ConstV @FlipConstV @Const Proxy doNil doUncons doCons . ConstV +-- > where +-- > doNil = impossible . unConstV +-- > doUncons l = bimap ConstV Const . flip trial l . unConstV +-- > doCons :: forall ℓ τ1 τ2 ρ1 ρ2. (KnownSymbol ℓ, Coercible τ1 τ2, AllUniqueLabels (Extend ℓ τ2 ρ2)) +-- > => Label ℓ -> Either (FlipConstV ρ1 ρ2) (Const τ1 τ2) +-- > -> FlipConstV (Extend ℓ τ1 ρ1) (Extend ℓ τ2 ρ2) +-- > doCons l (Left (FlipConstV v)) = FlipConstV $ extend @τ2 l v +-- > doCons l (Right (Const x)) = FlipConstV $ IsJust l (coerce @τ1 @τ2 x) +-- > \\ extendHas @ρ2 @ℓ @τ2 +coerceVar :: forall r1 r2. BiForall r1 r2 Coercible => Var r1 -> Var r2 +coerceVar = unsafeCoerce + +{-------------------------------------------------------------------- + Variant initialization +--------------------------------------------------------------------} + +-- | Initialize a variant from a producer function that accepts labels. If this +-- function returns more than one possibility, then one is chosen arbitrarily to +-- be the value in the variant. +fromLabels :: forall c ρ f. (Alternative f, Forall ρ c, AllUniqueLabels ρ) + => (forall l a. (KnownSymbol l, c a) => Label l -> f a) -> f (Var ρ) +fromLabels mk = getCompose $ metamorph @_ @ρ @c @Const @(Const ()) @(Compose f Var) @Proxy + Proxy doNil doUncons doCons (Const ()) + where doNil _ = Compose $ empty + doUncons _ _ = Const $ Const () + doCons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, AllUniqueLabels (Extend ℓ τ ρ)) + => Label ℓ -> Const (Compose f Var ρ) (Proxy τ) -> Compose f Var (Extend ℓ τ ρ) + doCons l (Const (Compose v)) = Compose $ IsJust l <$> mk l <|> extend @τ l <$> v + \\ extendHas @ℓ @τ @ρ + +-- | Initialize a variant over a `Map`. +fromLabelsMap :: forall c f g ρ. (Alternative f, Forall ρ c, AllUniqueLabels ρ) + => (forall l a. (KnownSymbol l, c a) => Label l -> f (g a)) -> f (Var (Map g ρ)) +fromLabelsMap f = fromLabels @(IsA c g) @(Map g ρ) @f inner + \\ mapForall @g @ρ @c + \\ uniqueMap @g @ρ + where inner :: forall l a. (KnownSymbol l, IsA c g a) => Label l -> f a + inner l = case as @c @g @a of As -> f l + +{-------------------------------------------------------------------- + Functions for variants of ApSingle +--------------------------------------------------------------------} + +newtype VApS x (fs :: Row (* -> *)) = VApS { unVApS :: Var (ApSingle fs x) } +newtype FlipApp (x :: *) (f :: * -> *) = FlipApp (f x) + +-- | A version of 'erase' that works even when the row-type of the variant argument +-- is of the form @ApSingle fs x@. +eraseSingle + :: forall (c :: (* -> *) -> Constraint) (fs :: Row (* -> *)) (x :: *) (y :: *) + . Forall fs c + => (forall f . (c f) => f x -> y) + -> Var (ApSingle fs x) + -> y +eraseSingle f = erase @(ActsOn c x) @(ApSingle fs x) @y g + \\ apSingleForall @x @fs @c + where + g :: forall a. ActsOn c x a => a -> y + g a = case actsOn @c @x @a of As' -> f a + +-- | Performs a functorial-like map over an 'ApSingle' variant. +-- In other words, it acts as a variant transformer to convert a variant of +-- @f x@ values to a variant of @f y@ values. If no constraint is needed, +-- instantiate the first type argument with 'Unconstrained1'. +mapSingle + :: forall (c :: (* -> *) -> Constraint) (fs :: Row (* -> *)) (x :: *) (y :: *) + . (Forall fs c) + => (forall f . (c f) => f x -> f y) + -> Var (ApSingle fs x) + -> Var (ApSingle fs y) +mapSingle f = unVApS . metamorph @_ @fs @c @Either @(VApS x) @(VApS y) @(FlipApp x) + Proxy doNil doUncons doCons . VApS + where + doNil = impossible . unVApS + + doUncons :: forall l f fs + . ( c f, fs .! l ≈ f, KnownSymbol l) + => Label l -> VApS x fs -> Either (VApS x (fs .- l)) (FlipApp x f) + doUncons l = bimap VApS FlipApp + . flip (trial \\ apSingleHas @x @l @f @fs) l + . unVApS + + doCons :: forall l f fs. (KnownSymbol l, c f, AllUniqueLabels (Extend l f fs)) + => Label l + -> Either (VApS y fs) (FlipApp x f) + -> VApS y (Extend l f fs) + doCons l (Right (FlipApp x)) = VApS $ IsJust l (f x) + \\ apSingleExtendSwap @y @l @f @fs + \\ extendHas @l @(f y) @(ApSingle fs y) + \\ uniqueApSingle @y @(Extend l f fs) + doCons l (Left (VApS v)) = VApS $ extend @(f y) l v + \\ apSingleExtendSwap @y @l @f @fs + +-- | A version of 'eraseZip' that works even when the row-types of the variant +-- arguments are of the form @ApSingle fs x@. +eraseZipSingle :: forall c fs (x :: *) (y :: *) z + . (Forall fs c) + => (forall f. c f => f x -> f y -> z) + -> Var (ApSingle fs x) -> Var (ApSingle fs y) -> Maybe z +eraseZipSingle f x y = getConst $ metamorph @_ @fs @c @Either + @(Product (VApS x) (VApS y)) @(Const (Maybe z)) @(Const (Maybe z)) + Proxy doNil doUncons doCons (Pair (VApS x) (VApS y)) + + where doNil :: Product (VApS x) (VApS y) Empty + -> Const (Maybe z) (Empty :: Row (* -> *)) + doNil (Pair (VApS z) _) = Const (impossible z) + + doUncons :: forall l f ρ + . (KnownSymbol l, c f, ρ .! l ≈ f) + => Label l + -> Product (VApS x) (VApS y) ρ + -> Either (Product (VApS x) (VApS y) (ρ .- l)) + (Const (Maybe z) f) + doUncons l (Pair (VApS r1) (VApS r2)) = + case ( + trial r1 l \\ apSingleHas @x @l @f @ρ, + trial r2 l \\ apSingleHas @y @l @f @ρ + ) of + (Right u, Right v) -> Right $ Const $ Just $ f @f u v + (Left us, Left vs) -> Left (Pair (VApS us) (VApS vs)) + _ -> Right $ Const Nothing + + doCons :: forall l (τ :: * -> *) ρ + . Label l + -> Either (Const (Maybe z) ρ) (Const (Maybe z) τ) + -> Const (Maybe z) (Extend l τ ρ) + doCons _ (Left (Const w)) = Const w + doCons _ (Right (Const w)) = Const w + +{-------------------------------------------------------------------- + Generic instance +--------------------------------------------------------------------} + +-- The generic structure we want Vars to have is not the hidden internal one, +-- but rather one that appears as a Haskell sum type. Thus, we can't derive +-- Generic automatically. +-- +-- The following Generic instance creates a representation of a Var that is +-- very similar to a native Haskell sum type except that the tree of possibilities (':+:') +-- that it produces will be extremely unbalanced. I don't think this is a problem. +-- Furthermore, because we don't want Vars to always have a trailing void option on +-- the end, we must have a special case for singleton Vars, which means that +-- we can't use metamorph. + +instance GenericVar r => G.Generic (Var r) where + type Rep (Var r) = + G.D1 ('G.MetaData "Var" "Data.Row.Variants" "row-types" 'False) (RepVar r) + from = G.M1 . fromVar + to = toVar . G.unM1 + +class GenericVar r where + type RepVar (r :: Row *) :: * -> * + fromVar :: Var r -> RepVar r x + toVar :: RepVar r x -> Var r + +instance GenericVar Empty where + type RepVar Empty = G.V1 + fromVar = impossible + toVar = \case + +instance KnownSymbol name => GenericVar (R '[name :-> t]) where + type RepVar (R (name :-> t ': '[])) = G.C1 + ('G.MetaCons name 'G.PrefixI 'False) + (G.S1 ('G.MetaSel 'Nothing 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) + (G.Rec0 t)) + fromVar (unSingleton -> (_, a)) = G.M1 (G.M1 (G.K1 a)) + toVar (G.M1 (G.M1 (G.K1 a))) = IsJust (Label @name) a + +instance + ( GenericVar (R (name' :-> t' ': r')) + , KnownSymbol name, Extend name t ('R (name' :-> t' ': r')) ≈ 'R (name :-> t ': (name' :-> t' ': r')) + , AllUniqueLabels (R (name :-> t ': (name' :-> t' ': r'))) + ) => GenericVar (R (name :-> t ': (name' :-> t' ': r'))) where + type RepVar (R (name :-> t ': (name' :-> t' ': r'))) = (G.C1 + ('G.MetaCons name 'G.PrefixI 'False) + (G.S1 ('G.MetaSel 'Nothing 'G.NoSourceUnpackedness 'G.NoSourceStrictness 'G.DecidedLazy) + (G.Rec0 t))) G.:+: RepVar (R (name' :-> t' ': r')) + fromVar v = case trial @name v Label of + Left v' -> G.R1 (fromVar v') + Right a -> G.L1 (G.M1 (G.M1 (G.K1 a))) + toVar (G.L1 (G.M1 (G.M1 (G.K1 a)))) = IsJust (Label @name) a + toVar (G.R1 g) = extend @t @name @('R (name' :-> t' ': r')) Label $ toVar g + +{-------------------------------------------------------------------- + Native data type compatibility +--------------------------------------------------------------------} + +-- $native +-- The 'toNative' and 'fromNative' functions allow one to convert between +-- 'Var's and regular Haskell data types ("native" types) that have the same +-- number of constructors such that each constructor has one field and the same +-- name as one of the options of the 'Var', which has the same type as that field. +-- As expected, they compose to form the identity. Alternatively, one may use +-- 'fromNativeGeneral', which allows a variant with excess options to still be +-- transformed to a native type. Because of this, 'fromNativeGeneral' requires a type +-- application (although 'fromNative' does not). The only requirement is that +-- the native Haskell data type be an instance of 'Generic'. +-- +-- For example, consider the following simple data type: +-- +-- >>> data Pet = Dog {age :: Int} | Cat {age :: Int} deriving (Generic, Show) +-- +-- Then, we have the following: +-- +-- >>> toNative $ IsJust (Label @"Dog") 3 :: Pet +-- Dog {age = 3} +-- >>> V.fromNative $ Dog 3 :: Var ("Dog" .== Int .+ "Cat" .== Int) +-- {Dog=3} + +type family NativeRow t where + NativeRow t = NativeRowG (G.Rep t) + +type family NativeRowG t where + NativeRowG (G.M1 G.D m cs) = NativeRowG cs + NativeRowG G.V1 = Empty + NativeRowG (l G.:+: r) = NativeRowG l .+ NativeRowG r + NativeRowG (G.C1 ('G.MetaCons name fixity sels) (G.S1 m (G.Rec0 t))) = name .== t + + +-- | Conversion helper to bring a variant back into a Haskell type. Note that the +-- native Haskell type must be an instance of 'Generic'. +class ToNativeG a where + toNative' :: Var (NativeRowG a) -> a x + +instance ToNativeG cs => ToNativeG (G.D1 m cs) where + toNative' = G.M1 . toNative' + +instance ToNativeG G.V1 where + toNative' = impossible + +instance (KnownSymbol name) + => ToNativeG (G.C1 ('G.MetaCons name fixity sels) + (G.S1 m (G.Rec0 t))) where + toNative' = G.M1 . G.M1 . G.K1 . snd . unSingleton + +instance ( ToNativeG l, ToNativeG r, (NativeRowG l .+ NativeRowG r) .\\ NativeRowG r ≈ NativeRowG l + , AllUniqueLabels (NativeRowG r), Forall (NativeRowG l) Unconstrained1) + => ToNativeG (l G.:+: r) where + toNative' v = case multiTrial @(NativeRowG r) @(NativeRowG (l G.:+: r)) v of + Left v' -> G.L1 $ toNative' v' + Right v' -> G.R1 $ toNative' v' + +type ToNative t = (G.Generic t, ToNativeG (G.Rep t)) + +-- | Convert a variant to a native Haskell type. +toNative :: ToNative t => Var (NativeRow t) -> t +toNative = G.to . toNative' + + +-- | Conversion helper to turn a Haskell variant into a row-types extensible +-- variant. Note that the native Haskell type must be an instance of 'Generic'. +class FromNativeG a where + fromNative' :: a x -> Var (NativeRowG a) + +instance FromNativeG cs => FromNativeG (G.D1 m cs) where + fromNative' (G.M1 v) = fromNative' v + +instance FromNativeG G.V1 where + fromNative' = \ case + +instance KnownSymbol name + => FromNativeG (G.C1 ('G.MetaCons name fixity sels) + (G.S1 m (G.Rec0 t))) where + fromNative' (G.M1 (G.M1 (G.K1 x))) = IsJust (Label @name) x + +instance (FromNativeG l, FromNativeG r) => FromNativeG (l G.:+: r) where + -- Ideally, we would use 'diversify' here instead of 'unsafeCoerce', but it + -- makes the constraints really hairy. + fromNative' (G.L1 x) = unsafeCoerce $ fromNative' @l x + fromNative' (G.R1 y) = unsafeCoerce $ fromNative' @r y + +type FromNative t = (G.Generic t, FromNativeG (G.Rep t)) + +-- | Convert a Haskell variant to a row-types Var. +fromNative :: FromNative t => t -> Var (NativeRow t) +fromNative = fromNative' . G.from + + +-- | Conversion helper to turn a Haskell variant into a row-types extensible +-- variant. Note that the native Haskell type must be an instance of 'Generic'. +class FromNativeGeneralG a ρ where + fromNativeGeneral' :: a x -> Var ρ + +instance FromNativeGeneralG cs ρ => FromNativeGeneralG (G.D1 m cs) ρ where + fromNativeGeneral' (G.M1 v) = fromNativeGeneral' v + +instance FromNativeGeneralG G.V1 ρ where + fromNativeGeneral' = \ case + +instance (KnownSymbol name, ρ .! name ≈ t, AllUniqueLabels ρ) + => FromNativeGeneralG (G.C1 ('G.MetaCons name fixity sels) + (G.S1 m (G.Rec0 t))) ρ where + fromNativeGeneral' (G.M1 (G.M1 (G.K1 x))) = IsJust (Label @name) x + +instance (FromNativeGeneralG l ρ, FromNativeGeneralG r ρ) + => FromNativeGeneralG (l G.:+: r) ρ where + -- Ideally, we would use 'diversify' here instead of 'unsafeCoerce', but it + -- makes the constraints really hairy. + fromNativeGeneral' (G.L1 x) = unsafeCoerce $ fromNativeGeneral' @l @ρ x + fromNativeGeneral' (G.R1 y) = unsafeCoerce $ fromNativeGeneral' @r @ρ y + +type FromNativeGeneral t ρ = (G.Generic t, FromNativeGeneralG (G.Rep t) ρ) + +-- | Convert a Haskell variant to a row-types Var. +fromNativeGeneral :: FromNativeGeneral t ρ => t -> Var ρ +fromNativeGeneral = fromNativeGeneral' . G.from + + +{-------------------------------------------------------------------- + Generic-lens compatibility +--------------------------------------------------------------------} + +-- | Every possibility of a row-types based variant has an 'AsConstructor' instance. +instance {-# OVERLAPPING #-} + ( AllUniqueLabels r + , AllUniqueLabels r' + , KnownSymbol name + , r .! name ≈ a + , r' .! name ≈ b + , r' ≈ (r .- name) .\/ (name .== b)) + => AsConstructor name (Var r) (Var r') a b where + _Ctor = focus (Label @name) + {-# INLINE _Ctor #-} + +instance {-# OVERLAPPING #-} + ( AllUniqueLabels r + , KnownSymbol name + , r .! name ≈ a + , r ≈ (r .- name) .\/ (name .== a)) + => AsConstructor' name (Var r) a where + _Ctor' = focus (Label @name) + {-# INLINE _Ctor' #-}
+ src/aeson/Data/Row/Aeson.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row.Aeson +-- +-- This module adds orphan Aeson instances for 'Rec' and 'Var'. +-- +----------------------------------------------------------------------------- + +module Data.Row.Aeson () where + +import Data.Aeson +import Data.Aeson.Encoding (pairStr) +import Data.Aeson.Types (typeMismatch) +import Data.List (intercalate) +import Data.Text (Text) +import qualified Data.Text as Text (pack) + +import Data.Row +import qualified Data.Row.Records as Rec +import qualified Data.Row.Variants as Var + +instance Forall r ToJSON => ToJSON (Rec r) where + toJSON = Object . Rec.eraseToHashMap @ToJSON toJSON + + toEncoding = + pairs . foldMap (uncurry pairStr) . Rec.eraseWithLabels @ToJSON toEncoding + +instance (AllUniqueLabels r, Forall r FromJSON) => FromJSON (Rec r) where + parseJSON (Object o) = do + r <- Rec.fromLabelsA @FromJSON $ \ l -> do x <- o .: (show' l) + x `seq` pure x + r `seq` pure r + + parseJSON v = typeMismatch msg v + where msg = "REC: {" ++ intercalate "," (labels @r @FromJSON) ++ "}" + +instance Forall r ToJSON => ToJSON (Var r) where + toJSON v = object [foo l] + where (l, foo) = Var.eraseWithLabels @ToJSON (\v l -> l .= v) v + +instance (AllUniqueLabels r, Forall r FromJSON) => FromJSON (Var r) where + parseJSON (Object o) = Var.fromLabels @FromJSON $ \ l -> o .: (show' l) + parseJSON v = typeMismatch msg v + where msg = "VAR: {" ++ intercalate "," (labels @r @FromJSON) ++ "}" + +show' :: Show a => a -> Text +show' = Text.pack . show
+ src/barbies/Data/Row/Barbies.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +----------------------------------------------------------------------------- +-- | +-- Module : Data.Row.Barbie +-- +-- This module adds Barbie instances for 'Rec' and 'Var'. +-- +----------------------------------------------------------------------------- + + +module Data.Row.Barbies () where + +import Data.Functor.Compose +import Data.Functor.Identity +import Data.Functor.Product +import Data.Row +import Data.Row.Dictionaries +import qualified Data.Row.Records as Rec +import qualified Data.Row.Variants as Var + +import Data.Functor.Barbie (FunctorB(..), TraversableB(..), DistributiveB(..), ApplicativeB(..), ConstraintsB(..)) +import qualified Barbies.Constraints as B + +-- | Barbies requires that the functor be the final argument of the type. So, +-- even though the real type is @Rec (Map f ρ)@, we must wrap it in a newtype +-- wrapper so that 'f' is at the end. +newtype BarbieRec (ρ :: Row *) (f :: * -> *) = BarbieRec { unBarbieRec :: Rec (Rec.Map f ρ) } +newtype BarbieVar (ρ :: Row *) (f :: * -> *) = BarbieVar { unBarbieVar :: Var (Var.Map f ρ) } + +instance FreeForall r => FunctorB (BarbieRec r) where + bmap f = BarbieRec . Rec.transform' @r f . unBarbieRec + +instance FreeForall r => TraversableB (BarbieRec r) where + btraverse :: forall e f g. Applicative e => (forall a. f a -> e (g a)) -> BarbieRec r f -> e (BarbieRec r g) + btraverse f = fmap BarbieRec . Rec.traverseMap @Unconstrained1 @e @f @g @r f . unBarbieRec + +instance FreeForall r => DistributiveB (BarbieRec r) where + bdistribute :: forall f g. Functor f => f (BarbieRec r g) -> BarbieRec r (Compose f g) + bdistribute = BarbieRec . Rec.compose @f @g @r . Rec.distribute @f @(Rec.Map g r) . fmap unBarbieRec + \\ freeForall @(Rec.Map g r) @(IsA Unconstrained1 g) \\ mapForall @g @r @Unconstrained1 + +instance (AllUniqueLabels r, FreeForall r) => ApplicativeB (BarbieRec r) where + bpure :: forall f. (forall a. f a) -> BarbieRec r f + bpure fa = BarbieRec $ runIdentity $ Rec.fromLabelsMapA @Unconstrained1 @Identity @f @r (const $ Identity fa) + + bprod :: forall f g. BarbieRec r f -> BarbieRec r g -> BarbieRec r (f `Product` g) + bprod (BarbieRec r1) (BarbieRec r2) = BarbieRec $ Rec.zipTransform @Unconstrained1 @r @f @g @(Product f g) Pair r1 r2 + +instance FreeForall r => ConstraintsB (BarbieRec r) where + type AllB c (BarbieRec r) = Forall r c + baddDicts :: forall c f. Forall r c => BarbieRec r f -> BarbieRec r (B.Dict c `Product` f) + baddDicts = BarbieRec . Rec.transform @c @r @f @(B.Dict c `Product` f) (Pair (B.Dict @c)) . unBarbieRec + + + +instance FreeForall r => FunctorB (BarbieVar r) where + bmap f = BarbieVar . Var.transform' @r f . unBarbieVar + +instance FreeForall r => TraversableB (BarbieVar r) where + btraverse :: forall e f g. Applicative e => (forall a. f a -> e (g a)) -> BarbieVar r f -> e (BarbieVar r g) + btraverse f = fmap BarbieVar . Var.traverseMap @Unconstrained1 @e @f @g @r f . unBarbieVar + +instance FreeForall r => ConstraintsB (BarbieVar r) where + type AllB c (BarbieVar r) = Forall r c + baddDicts :: forall c f. Forall r c => BarbieVar r f -> BarbieVar r (B.Dict c `Product` f) + baddDicts = BarbieVar . Var.transform @c @r @f @(B.Dict c `Product` f) (Pair (B.Dict @c)) . unBarbieVar