module Data.Row.Variants
(
Label(..)
, KnownSymbol, AllUniqueLabels, WellBehaved
, Var, Row, Empty, type (≈)
, HasType, pattern IsJust, singleton, unSingleton
, fromLabels
, type (.\), Lacks, type (.\/), diversify, type (.+)
, update, focus, Modify, rename, Rename
, impossible, trial, trial', multiTrial, view
, restrict, split
, type (.!), type (.-), type (.\\), type (.==)
, toNative, fromNative, fromNativeExact
, Map, map, map', transform, transform'
, Forall, erase, eraseWithLabels, eraseZip
, sequence
, compose, uncompose
, labels
, unsafeMakeVar, unsafeInjectFront
)
where
import Prelude hiding (map, sequence, zip)
import Control.Applicative
import Control.Arrow ((<<<), (+++), left, right)
import Control.DeepSeq (NFData(..), deepseq)
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
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 ()
unsafeMakeVar :: forall r l. KnownSymbol l => Label l -> r .! l -> Var r
unsafeMakeVar (toKey -> l) = OneOf l . HideType
impossible :: Var Empty -> a
impossible _ = error "Impossible! Somehow, a variant of nothing was produced."
singleton :: KnownSymbol l => Label l -> a -> Var (l .== a)
singleton = IsJust
unSingleton :: forall l a. KnownSymbol l => Var (l .== a) -> (Label l, a)
unSingleton (OneOf _ (HideType x)) = (l, unsafeCoerce x) where l = Label @l
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
diversify :: forall r' r. Var r -> Var (r .\/ r')
diversify = unsafeCoerce
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
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 :: (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
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))
trial' :: KnownSymbol l => Var r -> Label l -> Maybe (r .! l)
trial' = (either Just (const Nothing) .) . trial
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)
view :: KnownSymbol l => Label l -> Var r -> Maybe (r .! l)
view = flip trial'
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
restrict :: forall r r'. (WellBehaved r, Subset r r') => Var r' -> Maybe (Var r)
restrict = either Just (pure Nothing) . split
erase :: forall c ρ b. Forall ρ c => (forall a. c a => a -> b) -> Var ρ -> b
erase f = snd @String . eraseWithLabels @c f
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
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
newtype VMap (f :: * -> *) (ρ :: Row *) = VMap { unVMap :: Var (Map f ρ) }
newtype VMap2 (f :: * -> *) (g :: * -> *) (ρ :: Row *) = VMap2 { unVMap2 :: Var (Map f (Map g ρ)) }
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
map' :: forall f r. Forall r Unconstrained1 => (forall a. a -> f a) -> Var r -> Var (Map f r)
map' = map @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
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
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 (VMap x) = impossible x
doUncons l = right VMap . flip trial l . unVMap
doCons l (Left fx) = Compose $ unsafeMakeVar l <$> fx
doCons _ (Right (Compose v)) = Compose $ unsafeInjectFront <$> v
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 (VMap2 x) = impossible x
doUncons l = Compose +++ VMap2 <<< flip trial l . unVMap2
doCons l (Left x) = VMap $ unsafeMakeVar l x
doCons _ (Right (VMap v)) = VMap $ unsafeInjectFront v
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 (VMap x) = impossible x
doUncons l = right VMap . flip trial l . unVMap
doCons l (Left (Compose x)) = VMap2 $ unsafeMakeVar l x
doCons _ (Right (VMap2 v)) = VMap2 $ unsafeInjectFront v
unsafeInjectFront :: forall l a r. KnownSymbol l => Var (R r) -> Var (R (l :-> a ': r))
unsafeInjectFront = unsafeCoerce
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
doCons l (Right (Compose v)) = Compose $
unsafeMakeVar l <$> mk l <|> unsafeInjectFront <$> v
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
type family RepVar (r :: Row *) :: * -> * where
RepVar (R '[]) = G.V1
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))
RepVar (R (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 r)
class GenericVar r where
fromVar :: Var r -> RepVar r x
toVar :: RepVar r x -> Var r
instance GenericVar Empty where
fromVar = impossible
toVar = \case
instance KnownSymbol name => GenericVar (R '[name :-> t]) where
fromVar (unSingleton -> (_, a)) = G.M1 (G.M1 (G.K1 a))
toVar (G.M1 (G.M1 (G.K1 a))) = IsJust (Label @name) a
instance {-# OVERLAPPABLE #-}
( GenericVar (R r)
, KnownSymbol name
, r ~ (name' :-> t' ': r')
, AllUniqueLabels (R (name :-> t ': r))
) => GenericVar (R (name :-> t ': r)) where
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
class ToNative a ρ where
toNative' :: Var ρ -> a x
instance ToNative cs ρ => ToNative (G.D1 m cs) ρ where
toNative' = G.M1 . toNative'
instance ToNative G.V1 Empty where
toNative' = impossible
instance (KnownSymbol name, ρ ≈ name .== t)
=> ToNative (G.C1 ('G.MetaCons name fixity sels)
(G.S1 m (G.Rec0 t))) ρ where
toNative' = G.M1 . G.M1 . G.K1 . snd . unSingleton
instance ( ToNative l ρ₁, ToNative r ρ₂, ρ₂ ≈ ρ .\\ ρ₁, ρ ≈ ρ₁ .+ ρ₂
, AllUniqueLabels ρ₁, Forall ρ₂ Unconstrained1)
=> ToNative (l G.:+: r) ρ where
toNative' v = case multiTrial @ρ₁ @ρ v of
Left v' -> G.L1 $ toNative' @_ @ρ₁ v'
Right v' -> G.R1 $ toNative' @_ @ρ₂ v'
toNative :: forall t ρ. (G.Generic t, ToNative (G.Rep t) ρ) => Var ρ -> t
toNative = G.to . toNative'
class FromNative a ρ where
fromNative' :: a x -> Var ρ
instance FromNative cs ρ => FromNative (G.D1 m cs) ρ where
fromNative' (G.M1 v) = fromNative' v
instance FromNative G.V1 ρ where
fromNative' = \ case
instance (KnownSymbol name, ρ .! name ≈ t, AllUniqueLabels ρ)
=> FromNative (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 (FromNative l ρ, FromNative r ρ)
=> FromNative (l G.:+: r) ρ where
fromNative' (G.L1 x) = unsafeCoerce $ fromNative' @l @ρ x
fromNative' (G.R1 y) = unsafeCoerce $ fromNative' @r @ρ y
fromNative :: forall t ρ. (G.Generic t, FromNative (G.Rep t) ρ) => t -> Var ρ
fromNative = fromNative' . G.from
class FromNativeExact a ρ where
fromNativeExact' :: a x -> Var ρ
instance FromNativeExact cs ρ => FromNativeExact (G.D1 m cs) ρ where
fromNativeExact' (G.M1 v) = fromNativeExact' v
instance FromNativeExact G.V1 Empty where
fromNativeExact' = \ case
instance (KnownSymbol name, ρ ≈ name .== t)
=> FromNativeExact (G.C1 ('G.MetaCons name fixity sels)
(G.S1 m (G.Rec0 t))) ρ where
fromNativeExact' (G.M1 (G.M1 (G.K1 x))) = IsJust (Label @name) x
instance (FromNativeExact l ρ₁, FromNativeExact r ρ₂, ρ ≈ ρ₁ .+ ρ₂)
=> FromNativeExact (l G.:+: r) ρ where
fromNativeExact' (G.L1 x) = unsafeCoerce $ fromNativeExact' @l @ρ₁ x
fromNativeExact' (G.R1 y) = unsafeCoerce $ fromNativeExact' @r @ρ₂ y
fromNativeExact :: forall t ρ. (G.Generic t, FromNativeExact (G.Rep t) ρ) => t -> Var ρ
fromNativeExact = fromNativeExact' . G.from
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' #-}