packages feed

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)