data-effects-th-0.4.0.0: src/Data/Effect/TH/Internal.hs
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
-- SPDX-License-Identifier: MPL-2.0 AND BSD-3-Clause
{- |
Copyright : (c) 2023-2025 Sayo contributors
(c) 2010-2011 Patrick Bahr, Tom Hvitved
(c) 2020 Michael Szvetits
License : MPL-2.0 (see the LICENSE file) AND BSD-3-Clause
Maintainer : ymdfield@outlook.jp
-}
module Data.Effect.TH.Internal where
import Control.Arrow ((>>>))
import Control.Effect (Eff, Free, perform, perform', perform'', send)
import Control.Lens (Traversal', makeLenses, (%~), (.~), _head)
import Control.Monad (forM, forM_, replicateM, when)
import Control.Monad.Reader (ReaderT, ask)
import Control.Monad.Writer.CPS (WriterT, execWriterT, lift, tell)
import Data.Char (toLower)
import Data.Default (Default, def)
import Data.Effect (EffectOrder (FirstOrder, HigherOrder), FirstOrder, LabelOf, OrderOf)
import Data.Effect.OpenUnion (Has, In, (:>))
import Data.Effect.Tag (Tagged)
import Data.Either.Extra (mapLeft, maybeToEither)
import Data.Either.Validation (Validation, eitherToValidation, validationToEither)
import Data.Function ((&))
import Data.Functor (($>), (<&>))
import Data.List (foldl', uncons)
import Data.List.Extra (unsnoc)
import Data.Maybe (fromJust, isJust)
import Data.Text qualified as T
import Language.Haskell.TH (
BangType,
Body (NormalB),
Clause (Clause),
Con (ForallC, GadtC, InfixC, NormalC, RecC, RecGadtC),
Dec (DataD, FunD, NewtypeD, PragmaD),
DocLoc (ArgDoc, DeclDoc),
Exp (AppE, AppTypeE, ConE, SigE, VarE),
Info (TyConI),
Inline (Inline),
Pat (VarP),
Phases (AllPhases),
Pragma (InlineP),
RuleMatch (FunLike),
Specificity (SpecifiedSpec),
TyVarBndr (..),
TyVarBndrSpec,
Type,
conT,
getDoc,
mkName,
pprint,
putDoc,
varT,
)
import Language.Haskell.TH qualified as TH
import Language.Haskell.TH.Syntax (
Cxt,
Dec (SigD),
Name,
Q,
Quote (newName),
Type (
AppKindT,
AppT,
ArrowT,
ConT,
ForallT,
ImplicitParamT,
InfixT,
ParensT,
PromotedT,
SigT,
UInfixT,
VarT
),
addModFinalizer,
nameBase,
reify,
)
data EffectInfo = EffectInfo
{ eName :: Name
, eParamVars :: [TyVarBndr ()]
, eCarrier :: TyVarBndr ()
, eOps :: [OpInfo]
}
data OpInfo = OpInfo
{ opName :: Name
, opParamTypes :: [TH.Type]
, opDataType :: TH.Type
, opResultType :: TH.Type
, opTyVars :: [TyVarBndrSpec]
, opCarrier :: TyVarBndr ()
, opCxt :: Cxt
, opOrder :: EffectOrder
}
data EffectConf = EffectConf
{ opConf :: Name -> OpConf
, doesGenerateLabel :: Bool
, doesGenerateOrderInstance :: Bool
}
alterOpConf :: (OpConf -> OpConf) -> EffectConf -> EffectConf
alterOpConf f conf = conf{opConf = f . opConf conf}
data OpConf = OpConf
{ _normalPerformerConf :: Maybe PerformerConf
, _keyedPerformerConf :: Maybe PerformerConf
, _taggedPerformerConf :: Maybe PerformerConf
, _senderConf :: Maybe PerformerConf
}
data PerformerConf = PerformerConf
{ _performerName :: String
, _doesGeneratePerformerSignature :: Bool
, _performerDoc :: Maybe String -> Q (Maybe String)
, _performerArgDoc :: Int -> Maybe String -> Q (Maybe String)
}
performerConfs :: Traversal' OpConf PerformerConf
performerConfs f OpConf{..} = do
normal <- traverse f _normalPerformerConf
keyed <- traverse f _keyedPerformerConf
tagged <- traverse f _taggedPerformerConf
sender <- traverse f _senderConf
pure
OpConf
{ _normalPerformerConf = normal
, _keyedPerformerConf = keyed
, _taggedPerformerConf = tagged
, _senderConf = sender
}
makeLenses ''OpConf
makeLenses ''PerformerConf
noGenerateNormalPerformer :: EffectConf -> EffectConf
noGenerateNormalPerformer = alterOpConf $ normalPerformerConf .~ Nothing
{-# INLINE noGenerateNormalPerformer #-}
noGenerateKeyedPerformer :: EffectConf -> EffectConf
noGenerateKeyedPerformer = alterOpConf $ keyedPerformerConf .~ Nothing
{-# INLINE noGenerateKeyedPerformer #-}
noGenerateTaggedPerformer :: EffectConf -> EffectConf
noGenerateTaggedPerformer = alterOpConf $ taggedPerformerConf .~ Nothing
{-# INLINE noGenerateTaggedPerformer #-}
noGeneratePerformerSignature :: EffectConf -> EffectConf
noGeneratePerformerSignature =
alterOpConf $ performerConfs %~ doesGeneratePerformerSignature .~ False
{-# INLINE noGeneratePerformerSignature #-}
noGenerateLabel :: EffectConf -> EffectConf
noGenerateLabel conf = conf{doesGenerateLabel = False}
{-# INLINE noGenerateLabel #-}
noGenerateOrderInstance :: EffectConf -> EffectConf
noGenerateOrderInstance conf = conf{doesGenerateOrderInstance = False}
{-# INLINE noGenerateOrderInstance #-}
instance Default EffectConf where
def =
EffectConf
{ opConf = \opName ->
let conf =
PerformerConf
{ _performerName =
let effConName' = nameBase opName
(opNameInitial, opNameTail) = fromJust $ uncons effConName'
in if opNameInitial == ':'
then opNameTail
else effConName' & _head %~ toLower
, _doesGeneratePerformerSignature = True
, _performerDoc = pure
, _performerArgDoc = const pure
}
in OpConf
{ _normalPerformerConf = Just conf
, _keyedPerformerConf =
Just $ conf & performerName %~ (++ "'")
, _taggedPerformerConf =
Just $ conf & performerName %~ (++ "''")
, _senderConf =
Just $ conf & performerName %~ (++ "'_")
}
, doesGenerateLabel = True
, doesGenerateOrderInstance = True
}
type EffectGenerator =
ReaderT (EffectConf, Name, Info, DataInfo, EffectInfo) (WriterT [Dec] Q) ()
genEffect, genFOE, genHOE :: EffectGenerator
genEffect = do
(conf, _, _, _, eInfo) <- ask
genPerformers conf eInfo & lift & lift >>= tell
genLabel conf eInfo & lift & lift >>= tell
genFOE = do
genEffect
(conf, _, _, _, EffectInfo{..}) <- ask
let eData = foldl AppT (ConT eName) (map (VarT . tyVarName) eParamVars)
when (doesGenerateOrderInstance conf) do
[d|type instance OrderOf $(pure eData) = 'FirstOrder|] & lift & lift >>= tell
[d|instance FirstOrder $(pure eData)|] & lift & lift >>= tell
genHOE = do
genEffect
(conf, _, _, _, EffectInfo{..}) <- ask
let eData = foldl AppT (ConT eName) (map (VarT . tyVarName) eParamVars)
when (doesGenerateOrderInstance conf) do
[d|type instance OrderOf $(pure eData) = 'HigherOrder|] & lift & lift >>= tell
genPerformers :: EffectConf -> EffectInfo -> Q [Dec]
genPerformers EffectConf{..} EffectInfo{..} = do
execWriterT $ forM eOps \con@OpInfo{..} -> do
let OpConf{..} = opConf opName
forM_ _normalPerformerConf (genNormalPerformer con)
forM_ _keyedPerformerConf (genKeyedPerformer con)
forM_ _taggedPerformerConf (genTaggedPerformer con)
forM_ _senderConf (genSender con)
genLabel :: EffectConf -> EffectInfo -> Q [Dec]
genLabel EffectConf{..} EffectInfo{..} =
execWriterT $ when doesGenerateLabel do
let labelData = mkName $ nameBase eName ++ "Label"
eData = foldl AppT (ConT eName) (map (VarT . tyVarName) eParamVars)
[DataD [] labelData [] Nothing [] []] & tell
[d|type instance LabelOf $(pure eData) = $(conT labelData)|] & lift >>= tell
genNormalPerformer
:: OpInfo
-> PerformerConf
-> WriterT [Dec] Q ()
genNormalPerformer =
genPerformer
(VarE 'perform `AppE`)
(\opDataType es -> InfixT opDataType ''(:>) es)
id
genKeyedPerformer
:: OpInfo
-> PerformerConf
-> WriterT [Dec] Q ()
genKeyedPerformer eff conf = do
nKey <- newName "key" & lift
let key = VarT nKey
genPerformer
(VarE 'perform' `AppTypeE` key `AppE`)
( \opDataType es ->
ConT ''Has `AppT` key `AppT` opDataType `AppT` es
)
(PlainTV nKey SpecifiedSpec :)
eff
conf
genTaggedPerformer
:: OpInfo
-> PerformerConf
-> WriterT [Dec] Q ()
genTaggedPerformer conf eff = do
nTag <- newName "tag" & lift
let tag = VarT nTag
genPerformer
(VarE 'perform'' `AppTypeE` tag `AppE`)
( \opDataType es ->
InfixT (ConT ''Tagged `AppT` tag `AppT` opDataType) ''(:>) es
)
(PlainTV nTag SpecifiedSpec :)
conf
eff
genSender
:: OpInfo
-> PerformerConf
-> WriterT [Dec] Q ()
genSender =
genPerformer
(VarE 'send `AppE`)
(\opDataType es -> InfixT opDataType ''In es)
id
genPerformer
:: (Exp -> Exp)
-> (TH.Type -> TH.Type -> TH.Type)
-> ([TyVarBndrSpec] -> [TyVarBndrSpec])
-> OpInfo
-> PerformerConf
-> WriterT [Dec] Q ()
genPerformer performer performCxt alterFnSigTVs con@OpInfo{..} conf@PerformerConf{..} = do
genPerformerArmor performCxt alterFnSigTVs con conf \f -> do
args <- replicateM (length opParamTypes) (newName "x")
let body =
performer
( foldl' AppE (ConE opName) (map VarE args)
& if _doesGeneratePerformerSignature
then (`SigE` ((opDataType `AppT` f) `AppT` opResultType))
else id
)
pure $ Clause (map VarP args) (NormalB body) []
genPerformerArmor
:: (TH.Type -> TH.Type -> TH.Type)
-> ([TyVarBndrSpec] -> [TyVarBndrSpec])
-> OpInfo
-> PerformerConf
-> (Type -> Q Clause)
-> WriterT [Dec] Q ()
genPerformerArmor performCxt alterFnSigTVs OpInfo{..} PerformerConf{..} clause = do
let carrier = tyVarType opCarrier
free <- newName "ff" & lift
es <- newName "es" & lift
c <- newName "c" & lift
freeCxt <- [t|Free $(varT c) $(varT free)|] & lift
carrierCxt <- [t|$(pure carrier) ~ Eff $(varT free) $(varT es)|] & lift
let fnName = mkName _performerName
funSig =
SigD
fnName
( ForallT
(opTyVars ++ (opCarrier $> SpecifiedSpec) : map (\n -> PlainTV n () $> SpecifiedSpec) [es, free, c] & alterFnSigTVs)
(freeCxt : carrierCxt : performCxt opDataType (VarT es) : opCxt)
(arrowChain opParamTypes (carrier `AppT` opResultType))
)
funInline = PragmaD (InlineP fnName Inline FunLike AllPhases)
funDef <- FunD fnName <$> sequence [clause carrier & lift]
-- Put documents
lift $ addModFinalizer do
effDoc <- getDoc $ DeclDoc opName
_performerDoc effDoc >>= mapM_ \doc -> do
putDoc (DeclDoc fnName) doc
forM_ [0 .. length opParamTypes - 1] \i -> do
argDoc <- getDoc $ ArgDoc opName i
_performerArgDoc i argDoc >>= mapM_ \doc -> do
putDoc (ArgDoc fnName i) doc
-- Append declerations
when _doesGeneratePerformerSignature $ tell [funSig]
tell [funDef, funInline]
arrowChain :: (Foldable t) => t TH.Type -> TH.Type -> TH.Type
arrowChain = flip $ foldr \l r -> ArrowT `AppT` l `AppT` r
-- | A reified information of a datatype.
data DataInfo = DataInfo
{ dataCxt :: Cxt
, dataName :: Name
, dataTyVars :: [TyVarBndr ()]
, dataCons :: [ConInfo]
}
data ConInfo = ConInfo
{ conName :: Name
, conArgs :: [BangType]
, conGadtReturnType :: Maybe TH.Type
, conTyVars :: [TyVarBndrSpec]
, conCxt :: Cxt
}
reifyEffect :: Name -> Q (Info, DataInfo, EffectInfo)
reifyEffect name = do
info <- reify name
dataInfo <-
analyzeData info
& maybe (fail $ "Not datatype: ‘" <> pprint name <> "’") pure
effClsInfo <-
analyzeEffect dataInfo
& either (fail . T.unpack) pure
pure (info, dataInfo, effClsInfo)
analyzeEffect :: DataInfo -> Either T.Text EffectInfo
analyzeEffect DataInfo{..} = do
(initTyVars, resultType) <- unsnoc dataTyVars & maybeToEither "No result type variable."
(paramVars, carrier) <- unsnoc initTyVars & maybeToEither "No carrier type variable."
let analyzeOp :: ConInfo -> Validation [T.Text] OpInfo
analyzeOp ConInfo{..} = eitherToValidation do
(opDataType, opCarrier, opResultType) <-
maybe
( pure
( foldl' AppT (VarT dataName) (map tyVarType paramVars)
, carrier
, tyVarType resultType
)
)
decomposeGadtReturnType
conGadtReturnType
let removeCarrierTV :: [TyVarBndr a] -> [TyVarBndr a]
removeCarrierTV = filter ((tyVarName opCarrier /=) . tyVarName)
opTyVars =
if isJust conGadtReturnType
then removeCarrierTV conTyVars
else map (SpecifiedSpec <$) (removeCarrierTV paramVars) ++ conTyVars
opParamTypes = map snd conArgs
Right
OpInfo
{ opName = conName
, opParamTypes = opParamTypes
, opDataType = opDataType
, opResultType = opResultType
, opTyVars = opTyVars
, opCarrier = opCarrier
, opCxt = conCxt
, opOrder =
if any (tyVarName opCarrier `occurs`) opParamTypes
then HigherOrder
else FirstOrder
}
where
decomposeGadtReturnType
:: TH.Type -> Either [T.Text] (TH.Type, TyVarBndr (), TH.Type)
decomposeGadtReturnType =
unkindType >>> \case
e `AppT` SigT (VarT f) kf `AppT` x ->
Right (e, KindedTV f () kf, x)
e `AppT` VarT f `AppT` x ->
Right (e, PlainTV f (), x)
t ->
Left
[ "Unexpected form of GADT return type for the higher-order operation ‘"
<> T.pack (nameBase conName)
<> "’: "
<> T.pack (pprint t)
]
effCons <-
traverse analyzeOp dataCons
& validationToEither
& mapLeft T.unlines
pure
EffectInfo
{ eName = dataName
, eParamVars = paramVars
, eCarrier = carrier
, eOps = effCons
}
-- * Utility functions
{- The code before modification is licensed under the BSD3 License as
shown in [1]. The modified code, in its entirety, is licensed under
MPL 2.0. When redistributing, please ensure that you do not remove
the BSD3 License text as indicated in [1].
<https://hackage.haskell.org/package/effet-0.4.0.0/docs/src/Control.Effect.Machinery.TH.html>
[1] Copyright Michael Szvetits (c) 2020
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Michael Szvetits nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-}
-- | Pures the name of a type variable.
tyVarName :: TyVarBndr a -> Name
tyVarName (PlainTV n _) = n
tyVarName (KindedTV n _ _) = n
-- | Converts a type variable to a type.
tyVarType :: TyVarBndr a -> TH.Type
tyVarType (PlainTV n _) = VarT n
tyVarType (KindedTV n _ k) = SigT (VarT n) k
-- | Throws away all kind information from a type.
unkindTypeRec :: TH.Type -> TH.Type
unkindTypeRec = \case
ForallT vs ps t -> ForallT (fmap unkindTyVar vs) (fmap unkindTypeRec ps) (unkindTypeRec t)
AppT l r -> AppT (unkindTypeRec l) (unkindTypeRec r)
SigT t _ -> t
InfixT l n r -> InfixT (unkindTypeRec l) n (unkindTypeRec r)
UInfixT l n r -> UInfixT (unkindTypeRec l) n (unkindTypeRec r)
ParensT t -> ParensT (unkindTypeRec t)
AppKindT t _ -> unkindTypeRec t
ImplicitParamT s t -> ImplicitParamT s (unkindTypeRec t)
other -> other
unkindType :: TH.Type -> TH.Type
unkindType = \case
SigT t _ -> t
other -> other
-- | Throws away the kind information of a type variable.
unkindTyVar :: TyVarBndr a -> TyVarBndr a
unkindTyVar (KindedTV n s _) = PlainTV n s
unkindTyVar unkinded = unkinded
-- | Checks if a name m appears somewhere in a type.
occurs :: Name -> TH.Type -> Bool
occurs m = \case
ForallT _ cxt t -> m `occurs` t || any (m `occurs`) cxt
AppT l r -> m `occurs` l || m `occurs` r
SigT t _ -> m `occurs` t
VarT n -> n == m
ConT n -> n == m
PromotedT n -> n == m
InfixT l n r -> n == m || m `occurs` l || m `occurs` r
UInfixT l n r -> n == m || m `occurs` l || m `occurs` r
ParensT t -> m `occurs` t
AppKindT t _ -> m `occurs` t
ImplicitParamT _ t -> m `occurs` t
_ -> False
{- The code before modification is licensed under the BSD3 License as
shown in [1]. The modified code, in its entirety, is licensed under
MPL 2.0. When redistributing, please ensure that you do not remove
the BSD3 License text as indicated in [2].
<https://github.com/pa-ba/compdata/blob/master/src/Data/Comp/Derive/Utils.hs>
[2] Copyright (c) 2010--2011 Patrick Bahr, Tom Hvitved
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-}
{- |
This function abstracts away @newtype@ declaration, it turns them into
@data@ declarations.
-}
analyzeData :: Info -> Maybe DataInfo
analyzeData = \case
TyConI (NewtypeD cxt name args _ constr _) ->
Just $ DataInfo cxt name (map ($> ()) args) (normalizeCon constr)
TyConI (DataD cxt name args _ constrs _) ->
Just $ DataInfo cxt name (map ($> ()) args) (concatMap normalizeCon constrs)
_ -> Nothing
normalizeCon :: Con -> [ConInfo]
normalizeCon = \case
ForallC vars cxt constr ->
[con{conTyVars = vars, conCxt = cxt} | con <- normalizeNonForallCon constr]
con -> normalizeNonForallCon con
normalizeNonForallCon :: Con -> [ConInfo]
normalizeNonForallCon = \case
NormalC constr args -> [ConInfo constr args Nothing [] []]
RecC constr args -> [ConInfo constr (args <&> \(_, s, t) -> (s, t)) Nothing [] []]
InfixC a constr b -> [ConInfo constr [a, b] Nothing [] []]
GadtC cons args typ -> [ConInfo con args (Just typ) [] [] | con <- cons]
RecGadtC cons args typ ->
[ConInfo con (args <&> \(_, s, t) -> (s, t)) (Just typ) [] [] | con <- cons]
ForallC{} -> fail "Unexpected nested forall."