symantic-base-0.3.0.20211007: src/Symantic/Reify.hs
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-} -- For reifyTH
-- | Reify an Haskell value using type-directed normalisation-by-evaluation (NBE).
module Symantic.Reify where
import Control.Monad (Monad(..))
import qualified Data.Function as Fun
import qualified Language.Haskell.TH as TH
import Symantic.Class (Abstractable(..))
-- | 'ReifyReflect' witnesses the duality between @meta@ and @(repr a)@.
-- It indicates which type variables in @a@ are not to be instantiated
-- with the arrow type, and instantiates them to @(repr _)@ in @meta@.
-- This is directly taken from: http://okmij.org/ftp/tagless-final/course/TDPE.hs
--
-- * @meta@ instantiates polymorphic types of the original Haskell expression
-- with @(repr _)@ types, according to how 'ReifyReflect' is constructed
-- using 'base' and @('-->')@. This is obviously not possible
-- if the orignal expression uses monomorphic types (like 'Int'),
-- but remains possible with constrained polymorphic types (like @(Num i => i)@),
-- because @(i)@ can still be inferred to @(repr _)@,
-- whereas the finally chosen @(repr)@
-- (eg. 'E', or 'Identity', or 'TH.CodeQ', or ...)
-- can have a 'Num' instance.
-- * @(repr a)@ is the symantic type as it would have been,
-- had the expression been written with explicit 'lam's
-- instead of bare haskell functions.
-- DOC: http://okmij.org/ftp/tagless-final/cookbook.html#TDPE
-- DOC: http://okmij.org/ftp/tagless-final/NBE.html
-- DOC: https://www.dicosmo.org/Articles/2004-BalatDiCosmoFiore-Popl.pdf
data ReifyReflect repr meta a = ReifyReflect
{ -- | 'reflect' converts from a *represented* Haskell term of type @a@
-- to an object *representing* that value of type @a@.
reify :: meta -> repr a
-- | 'reflect' converts back an object *representing* a value of type @a@,
-- to the *represented* Haskell term of type @a@.
, reflect :: repr a -> meta
}
-- | The base of induction : placeholder for a type which is not the arrow type.
base :: ReifyReflect repr (repr a) a
base = ReifyReflect{reify = Fun.id, reflect = Fun.id}
-- | The inductive case : the arrow type.
infixr 8 -->
(-->) :: Abstractable repr =>
ReifyReflect repr m1 o1 -> ReifyReflect repr m2 o2 ->
ReifyReflect repr (m1 -> m2) (o1 -> o2)
r1 --> r2 = ReifyReflect
{ reify = \meta -> lam (reify r2 Fun.. meta Fun.. reflect r1)
, reflect = \repr -> reflect r2 Fun.. (.@) repr Fun.. reify r1
}
-- * Using TemplateHaskell to fully auto-generate 'ReifyReflect'
-- | @$(reifyTH 'Foo.bar)@ calls 'reify' on 'Foo.bar'
-- with an 'ReifyReflect' generated from the infered type of 'Foo.bar'.
reifyTH :: TH.Name -> TH.Q TH.Exp
reifyTH name = do
info <- TH.reify name
case info of
TH.VarI n (TH.ForallT _vs _ctx ty) _dec ->
[| reify $(genReifyReflect ty) $(return (TH.VarE n)) |]
where
genReifyReflect (TH.AppT (TH.AppT TH.ArrowT a) b) = [| $(genReifyReflect a) --> $(genReifyReflect b) |]
genReifyReflect TH.VarT{} = [| base |]