spectacle-1.0.0: src/Data/Type/Rec.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Type.Rec
( -- * Extensible Records Transformer
RecF (NilF, ConF),
getF,
setF,
-- ** Construction
concatF,
-- ** Maps
mapF,
sequenceF,
-- ** Destruction
foldMapF,
-- ** Pretty Printing
ppRecListed,
-- * Extensible Records
Rec,
pattern Nil,
pattern Con,
get,
set,
-- * Record Dictionaries
Evident (Evident, Trivial),
pattern NilE,
pattern ConE,
-- * HasDict
HasDict,
evident,
-- * Has
Has,
-- * Re-export
module Data.Ascript,
module Data.Name,
)
where
import Control.Applicative (liftA2)
import Data.Functor.Identity (Identity (Identity, runIdentity))
import Data.Hashable (Hashable (hashWithSalt), hashWithSalt)
import Data.Kind (Constraint, Type)
import Data.List (intercalate)
import GHC.TypeLits (KnownSymbol, Symbol)
import Prettyprinter (Doc, pretty, viaShow, (<+>))
import Prettyprinter.Render.Terminal (AnsiStyle)
import Data.Ascript (Ascribe, type (#))
import Data.Name (Name, inferName)
import Data.Type.List (type (++))
-- ---------------------------------------------------------------------------------------------------------------------
-- | Extensible record transformer.
--
-- @since 0.1.0.0
data RecF :: (k -> Type) -> [Ascribe Symbol k] -> Type where
NilF :: RecF f '[]
ConF :: Name s -> f a -> RecF f ctx -> RecF f (s # a ': ctx)
sequenceF :: Applicative f => RecF f ctx -> f (Rec ctx)
sequenceF NilF = pure Nil
sequenceF (ConF name field xs) = liftA2 (Con name) field (sequenceF xs)
mapF :: (forall s a. Name s -> f a -> g a) -> RecF f ctx -> RecF g ctx
mapF _ NilF = NilF
mapF f (ConF name field xs) = ConF name (f name field) (mapF f xs)
foldMapF :: Monoid m => (forall s a. Name s -> f a -> m) -> RecF f ctx -> m
foldMapF _ NilF = mempty
foldMapF k (ConF name field xs) = k name field <> foldMapF k xs
concatF :: RecF f ctx -> RecF f ctx' -> RecF f (ctx ++ ctx')
concatF NilF ys = ys
concatF (ConF name x xs) ys = ConF name x (concatF xs ys)
ppRecListed :: HasDict Show ctx => Rec ctx -> [Doc AnsiStyle]
ppRecListed rs =
case evident @Show rs of
ConE n x xs -> pretty n <+> "=" <+> viaShow x : ppRecListed xs
NilE -> []
-- ---------------------------------------------------------------------------------------------------------------------
-- | 'Rec' is an extensible record.
--
-- @since 0.1.0.0
type Rec ctx = RecF Identity ctx
-- | A synonym of 'NilT' specialize to 'Rec'.
--
-- @since 0.1.0.0
pattern Nil :: () => '[] ~ ctx => Rec ctx
pattern Nil = NilF
-- | A synonym of 'ConT' specialize to 'Rec'.
--
-- @since 0.1.0.0
pattern Con :: () => (s # a ': xs) ~ ctx => Name s -> a -> Rec xs -> Rec ctx
pattern Con name field xs = ConF name (Identity field) xs
{-# COMPLETE Nil, Con #-}
-- | @since 0.1.0.0
instance HasDict Eq ctx => Eq (Rec ctx) where
-- Nominal equality
Nil == Nil = True
(evident @Eq -> ConE _ x xs) == (evident @Eq -> ConE _ y ys)
| x == y = xs == ys
| otherwise = False
-- | @since 0.1.0.0
instance HasDict Show ctx => Show (Rec ctx) where
show Nil = "Rec {}"
show rs@Con {} = "Rec {" ++ intercalate "; " (go $ evident rs) ++ "}"
where
go :: forall x. Evident Show x -> [String]
go NilE = []
go (ConE name field xs) = (show name ++ " = " ++ show field) : go (evident @Show xs)
-- | @since 0.1.0.0
instance HasDict Hashable ctx => Hashable (Rec ctx) where
hashWithSalt salt rs = case evident @Hashable rs of
NilE -> salt
ConE _ x xs -> hashWithSalt (hashWithSalt salt x) xs
set :: Has s a ctx => Name s -> a -> Rec ctx -> Rec ctx
set name x = setF name (Identity x)
get :: Has s a ctx => Name s -> Rec ctx -> a
get n r = runIdentity (getF n r)
-- ---------------------------------------------------------------------------------------------------------------------
-- | @'Evident' c ctx@ captures dictionary evidence of @Rec ctx@ for the typeclass @c@.
--
-- @since 0.1.0.0
data Evident :: (Type -> Constraint) -> [Ascribe Symbol Type] -> Type where
Trivial :: Evident c '[]
Evident :: (c a, HasDict c ctx) => Rec (s # a ': ctx) -> Evident c (s # a ': ctx)
-- | A synonym of 'Nil' specialize to 'Evident'.
--
-- @since 0.1.0.0
pattern NilE :: () => '[] ~ ctx => Evident c ctx
pattern NilE = Trivial
-- | A synonym of 'Con' specialize to 'Evident'.
--
-- @since 0.1.0.0
pattern ConE :: () => (c a, HasDict c xs, (s # a ': xs) ~ ctx) => Name s -> a -> Rec xs -> Evident c ctx
pattern ConE name field xs = Evident (Con name field xs)
{-# COMPLETE NilE, ConE #-}
-- ---------------------------------------------------------------------------------------------------------------------
-- | @'HasDict' c ctx@ is what it means for a @Rec ctx@ to be an instance of @c@.
--
-- * @Rec CNil@ trivially fulfills any constraint.
--
-- * @Rec (x :< ctx)@ fulfills @c@ iff. @c a@ and @HasDict c (Rec ctx)@ are fulfilled.
--
-- @since 0.1.0.0
class HasDict c ctx where
evident :: Rec ctx -> Evident c ctx
-- | @since 0.1.0.0
instance HasDict c '[] where
evident = const Trivial
{-# INLINE CONLIKE evident #-}
-- | @since 0.1.0.0
instance (c a, HasDict c ctx) => HasDict c (s # a ': ctx) where
evident = Evident
{-# INLINE CONLIKE evident #-}
-- ---------------------------------------------------------------------------------------------------------------------
-- | @'Has' s a ctx@ is the constraint that a @'Rec' ctx@ have a field @s@ of type @a@.
--
-- @since 0.1.0.0
class Has s a ctx | ctx s -> a where
getF :: Name s -> RecF f ctx -> f a
setF :: Name s -> f a -> RecF f ctx -> RecF f ctx
-- | @since 0.1.0.0
instance {-# OVERLAPS #-} Has s a (s # a ': ctx) where
getF _ (ConF _ x _) = x
setF _ x (ConF name _ r) = ConF name x r
-- | @since 0.1.0.0
instance Has s a ctx => Has s a (s' # a' ': ctx) where
getF name (ConF _ _ r) = getF name r
setF name x (ConF name' y r) = ConF name' y (setF name x r)
-- |
--
-- @since 0.1.0.0
class ReflectRow ctx where
repeatRow :: (forall a. f a) -> RecF f ctx
-- | @since 0.1.0.0
instance ReflectRow '[] where
repeatRow _ = NilF
-- | @since 0.1.0.0
instance (KnownSymbol s, ReflectRow xs) => ReflectRow (s # x ': xs) where
repeatRow x = ConF inferName x (repeatRow x)