data-effects-th-0.2.0.0: src/Data/Effect/Key/TH.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
module Data.Effect.Key.TH where
import Control.Effect.Key (SendFOEBy, SendHOEBy)
import Control.Lens ((%~), (<&>), _Just, _head)
import Control.Monad (forM_)
import Control.Monad.Writer (execWriterT, tell)
import Data.Char (toLower)
import Data.Default (def)
import Data.Effect.Key (type (##>), type (#>))
import Data.Effect.TH (makeEffect', noDeriveHFunctor)
import Data.Effect.TH.Internal (
DataInfo,
EffClsInfo (EffClsInfo),
EffConInfo (EffConInfo),
EffectClassConf (EffectClassConf),
EffectConf (EffectConf, _keyedSenderGenConf),
EffectOrder (FirstOrder, HigherOrder),
MakeEffectConf,
SenderFunctionConf (SenderFunctionConf),
alterEffectConf,
ecEffs,
ecName,
ecParamVars,
effName,
genSenderArmor,
normalSenderGenConf,
senderFnName,
tyVarName,
_confByEffect,
_keyedSenderGenConf,
_senderFnName,
)
import Data.Function ((&))
import Data.List.Extra (stripSuffix)
import Data.Text qualified as T
import Formatting (sformat, string, (%))
import Language.Haskell.TH (
Body (NormalB),
Clause (Clause),
Dec (DataD, TySynD),
Exp (AppTypeE, VarE),
Info,
Name,
Q,
TyVarBndr (PlainTV),
Type (AppT, ConT, InfixT, VarT),
mkName,
nameBase,
)
import Language.Haskell.TH.Datatype.TyVarBndr (pattern BndrReq)
makeKeyedEffect :: [Name] -> [Name] -> Q [Dec]
makeKeyedEffect =
makeEffect'
(def & changeNormalSenderFnNameFormat)
genEffectKey
{-# INLINE makeKeyedEffect #-}
makeKeyedEffect_ :: [Name] -> [Name] -> Q [Dec]
makeKeyedEffect_ =
makeEffect'
(def & noDeriveHFunctor & changeNormalSenderFnNameFormat)
genEffectKey
{-# INLINE makeKeyedEffect_ #-}
changeNormalSenderFnNameFormat :: MakeEffectConf -> MakeEffectConf
changeNormalSenderFnNameFormat =
alterEffectConf $ normalSenderGenConf . _Just . senderFnName %~ (++ "'_")
{-# INLINE changeNormalSenderFnNameFormat #-}
genEffectKey :: EffectOrder -> Info -> DataInfo -> EffClsInfo -> EffectClassConf -> Q [Dec]
genEffectKey order _ _ EffClsInfo{..} EffectClassConf{..} = execWriterT do
let keyedOp = case order of
FirstOrder -> ''(#>)
HigherOrder -> ''(##>)
pvs = tyVarName <$> ecParamVars
ecNamePlain <-
removeLastApostrophe (nameBase ecName)
& maybe
( fail . T.unpack $
sformat
("No last apostrophe on the effect class ‘" % string % "’.")
(nameBase ecName)
)
pure
let keyDataName = mkName $ ecNamePlain ++ "Key"
key = ConT keyDataName
tell [DataD [] keyDataName [] Nothing [] []]
tell
[ TySynD
(mkName ecNamePlain)
(pvs <&> (`PlainTV` BndrReq))
(InfixT key keyedOp (foldl AppT (ConT ecName) (map VarT pvs)))
]
forM_ ecEffs \con@EffConInfo{..} -> do
let EffectConf{..} = _confByEffect effName
forM_ _keyedSenderGenConf \conf@SenderFunctionConf{..} -> do
let sendCxt effDataType carrier = case order of
FirstOrder -> ConT ''SendFOEBy `AppT` key `AppT` effDataType `AppT` carrier
HigherOrder -> ConT ''SendHOEBy `AppT` key `AppT` effDataType `AppT` carrier
genSenderArmor sendCxt id conf{_senderFnName = nameBase effName & _head %~ toLower} con \_f ->
pure $ Clause [] (NormalB $ VarE (mkName _senderFnName) `AppTypeE` key) []
removeLastApostrophe :: String -> Maybe String
removeLastApostrophe = stripSuffix "'"