packages feed

classy-effects-th-0.1.0.0: src/Control/Effect/Class/Machinery/TH.hs

-- 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/.

{- |
Copyright   :  (c) 2023 Yamada Ryo
License     :  MPL-2.0 (see the file LICENSE)
Maintainer  :  ymdfield@outlook.jp
Stability   :  experimental
Portability :  portable

This module provides @TemplateHaskell@ functions to generates automatically various data types and
instances that constitute the effect system supplied by the @classy-effects@ framework.
-}
module Control.Effect.Class.Machinery.TH where

import Control.Effect.Class.Machinery.TH.Internal (
    defaultEffDataNamer,
    generateEffect,
    generateEffectWith,
    generateOrderUnifiedEffDataTySyn,
    generateOrderUnifiedEffectClass,
    unifyEffTypeParams,
 )
import Control.Effect.Class.Machinery.TH.Send (deriveEffectSend)
import Control.Monad (unless, (<=<))
import Control.Monad.Writer (execWriterT, lift, tell)
import Data.Effect.Class.TH.Internal (
    EffectOrder (FirstOrder, HigherOrder),
    effMethods,
    reifyEffectInfo,
 )
import Data.Function ((&))
import Language.Haskell.TH (Dec, Name, Q, mkName, nameBase)

{- |
In addition to 'makeEffectF' and 'makeEffectH',
generate the order-unified empty effect class:

    @class (FoobarF ... f, FoobarH ... f) => Foobar ... f@

, and generate the order-unified effect data type synonym:

    @type Foobar ... = FoobarS ... :+: LiftIns (FoobarI ...)@
-}
makeEffect ::
    -- | A name of order-unified empty effect class generated newly
    String ->
    -- | The name of first-order effect class
    Name ->
    -- | The name of higher-order effect class
    Name ->
    Q [Dec]
makeEffect clsU clsF clsH = do
    makeEffectWith
        clsU
        (clsU <> "D")
        clsF
        (defaultEffDataNamer FirstOrder clsF)
        clsH
        (defaultEffDataNamer HigherOrder clsH)

{- |
Generate an /instruction/ data type and type and pattern synonyms for abbreviating
'Control.Effect.Class.LiftIns'.
-}
makeEffectF :: Name -> Q [Dec]
makeEffectF = generateEffect FirstOrder <=< reifyEffectInfo

-- | Generate a /signature/ data type and a 'Data.Comp.Multi.HFunctor.HFunctor' instance.
makeEffectH :: Name -> Q [Dec]
makeEffectH = generateEffect HigherOrder <=< reifyEffectInfo

{- |
In addition to 'makeEffectF' and 'makeEffectH',
generate the order-unified empty effect class:

    @class (FoobarF ... f, FoobarH ... f) => Foobar ... f@

, and generate the order-unified effect data type synonym:

    @type Foobar ... = FoobarS ... :+: LiftIns (FoobarI ...)@
-}
makeEffectWith ::
    -- | A name of order-unified empty effect class generated newly
    String ->
    -- | A name of type synonym of order-unified effect data type generated newly
    String ->
    -- | The name of first-order effect class
    Name ->
    -- | The name of instruction data type corresponding to the first-order effect class
    Name ->
    -- | The name of higher-order effect class
    Name ->
    -- | The name of signature data type corresponding to the higher-order effect class
    Name ->
    Q [Dec]
makeEffectWith clsU dataU clsF dataI clsH dataS =
    execWriterT do
        infoF <- reifyEffectInfo clsF & lift
        infoH <- reifyEffectInfo clsH & lift

        pvs <- unifyEffTypeParams infoF infoH & lift

        generateEffectWith FirstOrder dataI infoF & lift >>= tell
        generateEffectWith HigherOrder dataS infoH & lift >>= tell

        generateOrderUnifiedEffectClass infoF infoH pvs (mkName clsU) & lift >>= tell

        [generateOrderUnifiedEffDataTySyn dataI dataS pvs (mkName dataU)]
            & lift . sequence
            >>= tell

        pure ()

{- |
Generate an /instruction/ data type and type and pattern synonyms for abbreviating
'Control.Effect.Class.LiftIns'.
-}
makeEffectFWith :: String -> Name -> Q [Dec]
makeEffectFWith dataI = generateEffectWith FirstOrder (mkName dataI) <=< reifyEffectInfo

-- | Generate a /signature/ data type and a 'Data.Comp.Multi.HFunctor.HFunctor' instance.
makeEffectHWith :: String -> Name -> Q [Dec]
makeEffectHWith dataS = generateEffectWith HigherOrder (mkName dataS) <=< reifyEffectInfo

{- |
Derive an instance of the effect, with no methods, that handles via 'Control.Effect.Class.SendIns'/
'Control.Effect.Class.SendSig' instances.
-}
makeEmptyEffect :: Name -> Q [Dec]
makeEmptyEffect effClsName = do
    info <- reifyEffectInfo effClsName

    unless (null $ effMethods info) $
        fail ("The effect class \'" <> nameBase effClsName <> "\' is not empty.")

    sequence [deriveEffectSend info Nothing]

{- |
Generate the order-unified empty effect class:

    @class (FoobarF ... f, FoobarH ... f) => Foobar ... f@

, and derive an instance of the effect that handles via 'Control.Effect.Class.SendIns'/
'Control.Effect.Class.SendSig' instances.
-}
makeOrderUnifiedEffectClass :: Name -> Name -> String -> Q [Dec]
makeOrderUnifiedEffectClass clsF clsH clsU = do
    infoF <- reifyEffectInfo clsF
    infoH <- reifyEffectInfo clsH
    pvs <- unifyEffTypeParams infoF infoH
    generateOrderUnifiedEffectClass infoF infoH pvs (mkName clsU)