grisette-0.7.0.0: src/Grisette/Internal/TH/DeriveUnifiedInterface.hs
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Module : Grisette.Internal.TH.DeriveUnifiedInterface
-- Copyright : (c) Sirui Lu 2024
-- License : BSD-3-Clause (see the LICENSE file)
--
-- Maintainer : siruilu@cs.washington.edu
-- Stability : Experimental
-- Portability : GHC only
module Grisette.Internal.TH.DeriveUnifiedInterface
( TypeableMode (..),
PrimaryUnifiedConstraint (..),
UnifiedInstance (..),
deriveUnifiedInterfaceExtra,
deriveUnifiedInterface,
deriveUnifiedInterfaces,
deriveUnifiedInterface1Extra,
deriveUnifiedInterface1,
deriveUnifiedInterface1s,
deriveFunctorArgUnifiedInterfaceExtra,
deriveFunctorArgUnifiedInterface,
deriveFunctorArgUnifiedInterfaces,
)
where
import Control.Monad (unless)
import Data.Typeable (Typeable)
import Grisette.Internal.TH.DeriveInstanceProvider
( DeriveInstanceProvider (instanceDeclaration),
)
import Grisette.Internal.TH.DeriveTypeParamHandler
( DeriveTypeParamHandler (handleBody, handleTypeParams),
NatShouldBePositive (NatShouldBePositive),
SomeDeriveTypeParamHandler (SomeDeriveTypeParamHandler),
)
import Grisette.Internal.TH.DeriveWithHandlers (deriveWithHandlers)
import Grisette.Internal.TH.Util
( allSameKind,
classParamKinds,
concatPreds,
getTypeWithMaybeSubst,
tvIsMode,
tvIsStar,
tvIsStarToStar,
)
import Grisette.Unified.Internal.EvalModeTag (EvalModeTag)
import Grisette.Unified.Internal.Util (withMode)
import Language.Haskell.TH
( Dec,
Exp,
Inline (Inline),
Kind,
Name,
Phases (AllPhases),
Pred,
Q,
RuleMatch (FunLike),
Type (ConT),
appT,
conT,
instanceD,
lam1E,
newName,
normalB,
pragInlD,
valD,
varE,
varP,
)
import Language.Haskell.TH.Datatype.TyVarBndr (TyVarBndrUnit, kindedTV, tvKind)
-- | Add a 'Typeable' constraint to the modes.
data TypeableMode = TypeableMode
instance DeriveTypeParamHandler TypeableMode where
handleTypeParams n _ tys = do
unless (n == 1) $
fail $
"TypeableMode: unified type class should have exactly one type "
<> "parameter"
let numModeParam = length $ (filter (tvIsMode . fst . head)) $ fst <$> tys
newTys <-
if numModeParam == 0
then do
nm <- newName "mode"
return $
( [(kindedTV nm (ConT ''EvalModeTag), Nothing)],
Nothing
)
: tys
else
if numModeParam == 1
then return tys
else fail "TypeableMode: multiple mode type variables found"
mapM (uncurry handleMode) newTys
where
handleMode ::
[(TyVarBndrUnit, Maybe Type)] ->
Maybe [Pred] ->
Q ([(TyVarBndrUnit, Maybe Type)], Maybe [Pred])
handleMode [(tv, substTy)] preds | tvIsMode tv = do
typeable <- [t|Typeable $(getTypeWithMaybeSubst tv substTy)|]
return ([(tv, substTy)], concatPreds (Just [typeable]) preds)
handleMode tys preds = return (tys, preds)
handleBody _ _ = return []
-- | Add a primary unified constraint that applies to all the type parameters
-- with the desired kind.
data PrimaryUnifiedConstraint = PrimaryUnifiedConstraint Name Bool
instance DeriveTypeParamHandler PrimaryUnifiedConstraint where
handleTypeParams
n
(PrimaryUnifiedConstraint className ignoreIfAlreadyHandled)
tys = do
unless (n == 1) $
fail $
"TypeableMode: unified type class should have exactly one type "
<> "parameter"
kinds <- classParamKinds className
let modes = filter (tvIsMode . fst . head) $ fst <$> tys
case modes of
[] -> fail "PrimaryUnifiedConstraint: No mode type variable found"
[[md]] -> do
mdTy <- uncurry getTypeWithMaybeSubst md
mapM (uncurry $ handle kinds mdTy) tys
[_] ->
fail "PrimaryUnifiedConstraint: multiple mode type variables found"
_ ->
fail "PrimaryUnifiedConstraint: multiple mode type variables found"
where
handle ::
[Kind] ->
Type ->
[(TyVarBndrUnit, Maybe Type)] ->
Maybe [Pred] ->
Q ([(TyVarBndrUnit, Maybe Type)], Maybe [Pred])
handle _ _ [] preds = return ([], preds)
handle _ _ tys (Just preds)
| ignoreIfAlreadyHandled =
return (tys, Just preds)
handle _ _ tys _
| not (allSameKind (map fst tys)) =
fail
"PrimaryUnifiedConstraint: All type parameters must be aligned"
handle kinds modety tys preds
| ConT ''EvalModeTag : (tvKind . fst <$> tys) == kinds = do
ts <- mapM (uncurry getTypeWithMaybeSubst) tys
cls <-
foldl appT (appT (conT className) (return modety)) $
return <$> ts
return (tys, concatPreds (Just [cls]) preds)
handle _ _ tys preds = return (tys, preds)
handleBody (PrimaryUnifiedConstraint _ _) _ = return []
-- | Provide an instance for a unified interface.
data UnifiedInstance = UnifiedInstance
{ _cls :: Name,
_clsWithFunc :: Name,
_withFunc :: Name,
_withFunc1 :: Maybe Name
}
instance DeriveInstanceProvider UnifiedInstance where
instanceDeclaration
(UnifiedInstance cls clsWithFunc withFunc maybeWithFunc1)
tys'
ctx
ty' = do
unless (all ((== 1) . length) tys') $
fail "UnifiedInstance: only support classes with one type parameter"
unless (length ty' == 1) $
fail "UnifiedInstance: only support classes with one type parameter"
let tys = head <$> tys'
let modes =
map (uncurry getTypeWithMaybeSubst) $ filter (tvIsMode . fst) tys
let stars =
map (uncurry getTypeWithMaybeSubst) $ filter (tvIsStar . fst) tys
let starToStars =
map (uncurry getTypeWithMaybeSubst) $
filter (tvIsStarToStar . fst) tys
case modes of
[] -> fail "UnifiedInstance: no mode type variables found"
[md] -> do
sequence
[ instanceD
(return ctx)
[t|$(conT cls) $md $(return $ head ty')|]
[ body md clsWithFunc withFunc stars maybeWithFunc1 starToStars,
pragInlD clsWithFunc Inline FunLike AllPhases
]
]
_ -> fail "UnifiedInstance: multiple mode type variables found"
where
applyWithFunc :: Name -> Q Type -> Q Type -> Q Exp -> Q Exp
applyWithFunc withFunc mode var exp =
[|$(varE withFunc) @($mode) @($var) $exp|]
body ::
Q Type -> Name -> Name -> [Q Type] -> Maybe Name -> [Q Type] -> Q Dec
body mode clsWithFunc withFunc starVars maybeWithFunc1 starToStarVars =
do
var <- newName "r"
let arg = varP var
let branch = foldr (applyWithFunc withFunc mode) (varE var) starVars
let withModeFunc = 'withMode
case (maybeWithFunc1, starToStarVars) of
(_, []) -> do
let exp =
lam1E
arg
[|$(varE withModeFunc) @($mode) $branch $branch|]
valD (varP clsWithFunc) (normalB exp) []
(Just withFunc1, _) -> do
let branchWithFunc1 =
foldr (applyWithFunc withFunc1 mode) branch starToStarVars
let exp =
lam1E
arg
[|
$(varE withModeFunc)
@($mode)
$branchWithFunc1
$branchWithFunc1
|]
valD (varP clsWithFunc) (normalB exp) []
(Nothing, _) ->
fail $
"UnifiedInstance: withFunc1 is not provided, type have "
<> "functor type parameters"
-- | Derive an instance for a unified interface, with extra handlers.
deriveUnifiedInterfaceExtra ::
[SomeDeriveTypeParamHandler] ->
Name ->
Name ->
Name ->
Q [Dec]
deriveUnifiedInterfaceExtra extraHandlers cls withFunc name =
deriveWithHandlers
( extraHandlers
<> [ SomeDeriveTypeParamHandler TypeableMode,
SomeDeriveTypeParamHandler NatShouldBePositive,
SomeDeriveTypeParamHandler $ PrimaryUnifiedConstraint cls False
]
)
(UnifiedInstance cls withFunc withFunc Nothing)
True
0
[name]
-- | Derive an instance for a unified interface.
deriveUnifiedInterface :: Name -> Name -> Name -> Q [Dec]
deriveUnifiedInterface = deriveUnifiedInterfaceExtra []
-- | Derive instances for a list of types for a unified interface.
deriveUnifiedInterfaces :: Name -> Name -> [Name] -> Q [Dec]
deriveUnifiedInterfaces cls withFunc =
fmap concat . mapM (deriveUnifiedInterface cls withFunc)
-- | Derive an instance for a unified interface for functors, with extra
-- handlers.
deriveUnifiedInterface1Extra ::
[SomeDeriveTypeParamHandler] ->
Name ->
Name ->
Name ->
Name ->
Name ->
Q [Dec]
deriveUnifiedInterface1Extra extraHandlers cls withFunc cls1 withFunc1 name =
deriveWithHandlers
( extraHandlers
<> [ SomeDeriveTypeParamHandler TypeableMode,
SomeDeriveTypeParamHandler NatShouldBePositive,
SomeDeriveTypeParamHandler $ PrimaryUnifiedConstraint cls False,
SomeDeriveTypeParamHandler $ PrimaryUnifiedConstraint cls1 False
]
)
(UnifiedInstance cls1 withFunc1 withFunc (Just withFunc1))
True
1
[name]
-- | Derive an instance for a unified interface for functors.
deriveUnifiedInterface1 ::
Name -> Name -> Name -> Name -> Name -> Q [Dec]
deriveUnifiedInterface1 = deriveUnifiedInterface1Extra []
-- | Derive instances for a list of types for a unified interface for functors.
deriveUnifiedInterface1s ::
Name -> Name -> Name -> Name -> [Name] -> Q [Dec]
deriveUnifiedInterface1s cls withFunc cls1 withFunc1 =
fmap concat . mapM (deriveUnifiedInterface1 cls withFunc cls1 withFunc1)
-- | Derive an instance for a unified interface, with extra handlers. The type
-- being derived may have functor type parameters.
deriveFunctorArgUnifiedInterfaceExtra ::
[SomeDeriveTypeParamHandler] -> Name -> Name -> Name -> Name -> Name -> Q [Dec]
deriveFunctorArgUnifiedInterfaceExtra
extraHandlers
cls
withFunc
cls1
withFunc1
name =
deriveWithHandlers
( extraHandlers
<> [ SomeDeriveTypeParamHandler TypeableMode,
SomeDeriveTypeParamHandler NatShouldBePositive,
SomeDeriveTypeParamHandler $ PrimaryUnifiedConstraint cls False,
SomeDeriveTypeParamHandler $ PrimaryUnifiedConstraint cls1 False
]
)
(UnifiedInstance cls withFunc withFunc (Just withFunc1))
True
0
[name]
-- | Derive an instance for a unified interface. The type being derived may have
-- functor type parameters.
deriveFunctorArgUnifiedInterface ::
Name -> Name -> Name -> Name -> Name -> Q [Dec]
deriveFunctorArgUnifiedInterface = deriveFunctorArgUnifiedInterfaceExtra []
-- | Derive instances for a list of types for a unified interface. The types
-- being derived may have functor type parameters.
deriveFunctorArgUnifiedInterfaces ::
Name -> Name -> Name -> Name -> [Name] -> Q [Dec]
deriveFunctorArgUnifiedInterfaces cls withFunc cls1 withFunc1 =
fmap concat
. mapM (deriveFunctorArgUnifiedInterface cls withFunc cls1 withFunc1)