packages feed

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 |]