packages feed

heftia-0.4.0.0: src/Control/Monad/Hefty/Transform.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- SPDX-License-Identifier: MPL-2.0

{- |
Copyright   :  (c) 2024 Sayo Koyoneda
License     :  MPL-2.0 (see the LICENSE file)
Maintainer  :  ymdfield@outlook.jp

This module provides functions for transforming effects.
Please refer to the documentation of the [top-level module]("Control.Monad.Hefty").
-}
module Control.Monad.Hefty.Transform where

import Control.Effect (type (~>))
import Control.Monad.Hefty.Interpret (iterAllEffHFBy)
import Control.Monad.Hefty.Types (Eff, sendUnionBy, sendUnionHBy)
import Data.Effect.HFunctor (HFunctor)
import Data.Effect.Key (Key (Key, unKey), KeyH (KeyH, unKeyH), type (##>), type (#>))
import Data.Effect.OpenUnion.Internal (
    BundleUnder,
    Drop,
    IsSuffixOf,
    Split,
    Strengthen,
    StrengthenN,
    StrengthenNUnder,
    StrengthenUnder,
    Take,
    WeakenN,
    WeakenNUnder,
    WeakenUnder,
 )
import Data.Effect.OpenUnion.Internal.FO (
    Union,
    bundleAllUnion,
    bundleUnion,
    bundleUnionN,
    bundleUnionUnder,
    decomp,
    inj,
    prj,
    strengthen,
    strengthenN,
    strengthenNUnder,
    strengthenUnder,
    strengthens,
    strengthensUnder,
    unbundleAllUnion,
    unbundleUnion,
    unbundleUnionN,
    unbundleUnionUnder,
    weaken,
    weakenN,
    weakenNUnder,
    weakenUnder,
    weakens,
    weakensUnder,
    type (<|),
 )
import Data.Effect.OpenUnion.Internal.HO (
    UnionH,
    bundleAllUnionH,
    bundleUnionH,
    bundleUnionUnderH,
    decompH,
    hfmapUnion,
    injH,
    prjH,
    strengthenH,
    strengthenNH,
    strengthenNUnderH,
    strengthenUnderH,
    strengthensH,
    unbundleAllUnionH,
    unbundleUnionH,
    unbundleUnionUnderH,
    weakenH,
    weakenNH,
    weakenNUnderH,
    weakenUnderH,
    weakensH,
    type (<<|),
 )
import Data.Effect.Tag (Tag (Tag, unTag), TagH (TagH, unTagH), type (#), type (##))
import GHC.TypeNats (KnownNat)

-- * Rewriting effectful operations

{- | Transforms the first-order effect @e@ at the head of the list into another
first-order effect @e'@.
-}
transform
    :: forall e e' ef eh
     . (e ~> e')
    -> Eff eh (e ': ef) ~> Eff eh (e' ': ef)
transform f = transEff (either weaken (inj . f) . decomp)
{-# INLINE transform #-}

{- | Transforms the higher-order effect @e@ at the head of the list into another
higher-order effect @e'@.
-}
transformH
    :: forall e e' eh ef
     . (HFunctor e)
    => (e (Eff (e' ': eh) ef) ~> e' (Eff (e' ': eh) ef))
    -> Eff (e ': eh) ef ~> Eff (e' ': eh) ef
transformH f = transEffH (either weakenH (injH . f) . decompH)
{-# INLINE transformH #-}

{- | Transforms the first-order effect @e@ at the head of the list into another
first-order effect @e'@ and embeds it into the list.

If multiple instances of @e'@ exist in the list, the one closest to the head
(with the smallest index) will be targeted.
-}
translate
    :: forall e e' ef eh
     . (e' <| ef)
    => (e ~> e')
    -> Eff eh (e ': ef) ~> Eff eh ef
translate f = transEff (either id (inj . f) . decomp)
{-# INLINE translate #-}

{- | Transforms the higher-order effect @e@ at the head of the list into another
higher-order effect @e'@ and embeds it into the list.

If multiple instances of @e'@ exist in the list, the one closest to the head
(with the smallest index) will be targeted.
-}
translateH
    :: forall e e' eh ef
     . (e' <<| eh, HFunctor e)
    => (e (Eff eh ef) ~> e' (Eff eh ef))
    -> Eff (e ': eh) ef ~> Eff eh ef
translateH f = transEffH (either id (injH . f) . decompH)
{-# INLINE translateH #-}

{- | Rewrites the first-order effect @e@ in the list.

If multiple instances of @e@ exist in the list, the one closest to the head
(with the smallest index) will be targeted.
-}
rewrite
    :: forall e ef eh
     . (e <| ef)
    => (e ~> e)
    -> Eff eh ef ~> Eff eh ef
rewrite f = transEff \u -> maybe u (inj . f) $ prj @e u
{-# INLINE rewrite #-}

{- | Rewrites the higher-order effect @e@ in the list.

If multiple instances of @e@ exist in the list, the one closest to the head
(with the smallest index) will be targeted.
-}
rewriteH
    :: forall e eh ef
     . (e <<| eh, HFunctor e)
    => (e (Eff eh ef) ~> e (Eff eh ef))
    -> Eff eh ef ~> Eff eh ef
rewriteH f = transEffH \u -> maybe u (injH . f) $ prjH @e u
{-# INLINE rewriteH #-}

-- | Transforms all first-order effects in the open union at once.
transEff
    :: forall ef ef' eh
     . (Union ef ~> Union ef')
    -> Eff eh ef ~> Eff eh ef'
transEff = transEffHF id
{-# INLINE transEff #-}

-- | Transforms all higher-order effects in the open union at once.
transEffH
    :: forall eh eh' ef
     . (UnionH eh (Eff eh' ef) ~> UnionH eh' (Eff eh' ef))
    -> Eff eh ef ~> Eff eh' ef
transEffH f = transEffHF f id
{-# INLINE transEffH #-}

{- | Transforms all higher-order and first-order effects in the open union at
once.
-}
transEffHF
    :: forall eh eh' ef ef'
     . (UnionH eh (Eff eh' ef') ~> UnionH eh' (Eff eh' ef'))
    -> (Union ef ~> Union ef')
    -> Eff eh ef ~> Eff eh' ef'
transEffHF fh ff = loop
  where
    loop :: Eff eh ef ~> Eff eh' ef'
    loop =
        iterAllEffHFBy
            pure
            (flip sendUnionHBy . fh . hfmapUnion loop)
            (flip sendUnionBy . ff)
{-# INLINE transEffHF #-}

-- * Manipulating the effect list (without rewriting effectful operations)

-- ** Insertion functions

-- | Adds an arbitrary first-order effect @e@ to the head of the list.
raise :: forall e ef eh. Eff eh ef ~> Eff eh (e ': ef)
raise = transEff weaken
{-# INLINE raise #-}

-- | Adds multiple arbitrary first-order effects to the head of the list.
raises :: (ef `IsSuffixOf` ef') => Eff eh ef ~> Eff eh ef'
raises = transEff weakens
{-# INLINE raises #-}

{- | Adds a specified number @len@ of arbitrary first-order effects to the head
of the list.
-}
raiseN
    :: forall len ef ef' eh
     . (WeakenN len ef ef')
    => Eff eh ef ~> Eff eh ef'
raiseN = transEff (weakenN @len)
{-# INLINE raiseN #-}

{- | Inserts an arbitrary first-order effect @e2@ just below the head of the
list.
-}
raiseUnder
    :: forall e1 e2 ef eh
     . Eff eh (e1 ': ef) ~> Eff eh (e1 ': e2 ': ef)
raiseUnder = transEff weakenUnder
{-# INLINE raiseUnder #-}

{- | Inserts multiple arbitrary first-order effects at a position @offset@ steps
below the head of the list.
-}
raisesUnder
    :: forall offset ef ef' eh
     . (WeakenUnder offset ef ef')
    => Eff eh ef ~> Eff eh ef'
raisesUnder = transEff (weakensUnder @offset)
{-# INLINE raisesUnder #-}

{- | Inserts @len@ arbitrary first-order effects at a position @offset@ steps
below the head of the list.
-}
raiseNUnder
    :: forall len offset ef ef' eh
     . (WeakenNUnder len offset ef ef')
    => Eff eh ef ~> Eff eh ef'
raiseNUnder = transEff (weakenNUnder @len @offset)
{-# INLINE raiseNUnder #-}

{- | Adds a specified number @len@ of arbitrary higher-order effects to the head
of the list.
-}
raiseH :: forall e eh ef. Eff eh ef ~> Eff (e ': eh) ef
raiseH = transEffH weakenH
{-# INLINE raiseH #-}

{- | Inserts an arbitrary higher-order effect @e2@ just below the head of the
list.
-}
raisesH :: (eh `IsSuffixOf` eh') => Eff eh ef ~> Eff eh' ef
raisesH = transEffH weakensH
{-# INLINE raisesH #-}

{- | Adds a specified number @len@ of arbitrary higher-order effects to the head
of the list.
-}
raiseNH
    :: forall len eh eh' ef
     . (WeakenN len eh eh')
    => Eff eh ef ~> Eff eh' ef
raiseNH = transEffH (weakenNH @len)
{-# INLINE raiseNH #-}

{- | Inserts an arbitrary higher-order effect @e2@ just below the head of the
list.
-}
raiseUnderH
    :: forall e1 e2 eh ef
     . Eff (e1 ': eh) ef ~> Eff (e1 ': e2 ': eh) ef
raiseUnderH = transEffH weakenUnderH
{-# INLINE raiseUnderH #-}

{- | Inserts @len@ arbitrary higher-order effects at a position @offset@ steps
below the head of the list.
-}
raiseNUnderH
    :: forall len offset eh eh' ef
     . (WeakenNUnder len offset eh eh')
    => Eff eh ef ~> Eff eh' ef
raiseNUnderH = transEffH (weakenNUnderH @len @offset)
{-# INLINE raiseNUnderH #-}

-- ** Merging functions

{- | Merges the first first-order effect @e@ at the head of the list into the
same type of effect @e@ that is below it.

If multiple candidates of @e@ exist in the list, the one closest to the head
(with the smallest index) will be targeted.
-}
subsume
    :: forall e ef eh
     . (e <| ef)
    => Eff eh (e ': ef) ~> Eff eh ef
subsume = transEff strengthen
{-# INLINE subsume #-}

{- | Merges multiple first-order effects at the head of the list into effects of
the same types that are below them.
-}
subsumes
    :: forall ef ef' eh
     . (Strengthen ef ef')
    => Eff eh ef ~> Eff eh ef'
subsumes = transEff strengthens
{-# INLINE subsumes #-}

{- | Merges a specified number @len@ of first-order effects at the head of the
list into effects of the same types that are below them.
-}
subsumeN
    :: forall len ef ef' eh
     . (StrengthenN len ef ef')
    => Eff eh ef ~> Eff eh ef'
subsumeN = transEff (strengthenN @len)
{-# INLINE subsumeN #-}

{- | Merges the first-order effect @e2@ located just below the head into the
same type of effect @e2@ that is below it.
-}
subsumeUnder
    :: forall e2 e1 ef eh
     . (e2 <| ef)
    => Eff eh (e1 ': e2 ': ef) ~> Eff eh (e1 ': ef)
subsumeUnder = transEff strengthenUnder
{-# INLINE subsumeUnder #-}

{- | Merges multiple first-order effects at an @offset@ below the head into
effects of the same types that are below them.
-}
subsumesUnder
    :: forall offset ef ef' eh
     . (StrengthenUnder offset ef ef')
    => Eff eh ef ~> Eff eh ef'
subsumesUnder = transEff (strengthensUnder @offset)
{-# INLINE subsumesUnder #-}

{- | Merges @len@ first-order effects at an @offset@ below the head into effects
of the same types that are below them.
-}
subsumeNUnder
    :: forall len offset ef ef' eh
     . (StrengthenNUnder len offset ef ef')
    => Eff eh ef ~> Eff eh ef'
subsumeNUnder = transEff (strengthenNUnder @len @offset)
{-# INLINE subsumeNUnder #-}

{- | Merges the first higher-order effect @e@ at the head of the list into the
same type of effect @e@ that is below it.

If multiple candidates of @e@ exist in the list, the one closest to the head
(with the smallest index) will be targeted.
-}
subsumeH
    :: forall e eh ef
     . (e <<| eh)
    => Eff (e ': eh) ef ~> Eff eh ef
subsumeH = transEffH strengthenH
{-# INLINE subsumeH #-}

{- | Merges multiple higher-order effects at the head of the list into effects of
the same types that are below them.
-}
subsumesH
    :: forall eh eh' ef
     . (Strengthen eh eh')
    => Eff eh ef ~> Eff eh' ef
subsumesH = transEffH strengthensH
{-# INLINE subsumesH #-}

{- | Merges a specified number @len@ of higher-order effects at the head of the
list into effects of the same types that are below them.
-}
subsumeNH
    :: forall len eh eh' ef
     . (StrengthenN len eh eh')
    => Eff eh ef ~> Eff eh' ef
subsumeNH = transEffH (strengthenNH @len)
{-# INLINE subsumeNH #-}

{- | Merges the higher-order effect @e2@ located just below the head into the
same type of effect @e2@ that is below it.
-}
subsumeUnderH
    :: forall e2 e1 eh ef
     . (e2 <<| eh)
    => Eff (e1 ': e2 ': eh) ef ~> Eff (e1 ': eh) ef
subsumeUnderH = transEffH strengthenUnderH
{-# INLINE subsumeUnderH #-}

{- | Merges @len@ higher-order effects at an @offset@ below the head into effects
of the same types that are below them.
-}
subsumeNUnderH
    :: forall len offset eh eh' ef
     . (StrengthenNUnder len offset eh eh')
    => Eff eh ef ~> Eff eh' ef
subsumeNUnderH = transEffH (strengthenNUnderH @len @offset)
{-# INLINE subsumeNUnderH #-}

-- ** Bundling functions

{- | Bundles several effects at the head of the list into a single element using
an open union.
-}
bundle
    :: forall ef bundle rest eh
     . (Split ef bundle rest)
    => Eff eh ef ~> Eff eh (Union bundle ': rest)
bundle = transEff bundleUnion
{-# INLINE bundle #-}

{- | Bundles the first @len@ effects at the head of the list into a single
element using an open union.
-}
bundleN
    :: forall len ef eh
     . (KnownNat len)
    => Eff eh ef ~> Eff eh (Union (Take len ef) ': Drop len ef)
bundleN = transEff (bundleUnionN @len)
{-# INLINE bundleN #-}

-- | Expands effects that have been bundled into an open union.
unbundle
    :: forall ef bundle rest eh
     . (Split ef bundle rest)
    => Eff eh (Union bundle ': rest) ~> Eff eh ef
unbundle = transEff unbundleUnion
{-# INLINE unbundle #-}

-- | Expands the first @len@ effects that have been bundled into an open union.
unbundleN
    :: forall len ef eh
     . (KnownNat len)
    => Eff eh (Union (Take len ef) ': Drop len ef) ~> Eff eh ef
unbundleN = transEff (unbundleUnionN @len)
{-# INLINE unbundleN #-}

{- | Expands effects at an @offset@ below the head of the list into a single
element using an open union.
-}
bundleUnder
    :: forall offset bundle ef ef' eh
     . (BundleUnder Union offset ef ef' bundle)
    => Eff eh ef ~> Eff eh ef'
bundleUnder = transEff (bundleUnionUnder @offset)
{-# INLINE bundleUnder #-}

-- TODO: add *bundle*N(H) functions

{- | Expands effects that have been bundled into an open union at an @offset@
below the head of the list.
-}
unbundleUnder
    :: forall offset bundle ef ef' eh
     . (BundleUnder Union offset ef ef' bundle)
    => Eff eh ef' ~> Eff eh ef
unbundleUnder = transEff (unbundleUnionUnder @offset)
{-# INLINE unbundleUnder #-}

-- | Bundles all first-order effects into a single open union.
bundleAll :: Eff eh ef ~> Eff eh '[Union ef]
bundleAll = transEff bundleAllUnion
{-# INLINE bundleAll #-}

-- | Expands all first-order effects from a single open union.
unbundleAll :: Eff eh '[Union ef] ~> Eff eh ef
unbundleAll = transEff unbundleAllUnion
{-# INLINE unbundleAll #-}

{- | Bundles several effects at the head of the list into a single element using
an open union.
-}
bundleH
    :: forall eh bundle rest ef
     . (Split eh bundle rest)
    => Eff eh ef ~> Eff (UnionH bundle ': rest) ef
bundleH = transEffH bundleUnionH
{-# INLINE bundleH #-}

-- | Expands effects that have been bundled into an open union.
unbundleH
    :: forall eh bundle rest ef
     . (Split eh bundle rest)
    => Eff (UnionH bundle ': rest) ef ~> Eff eh ef
unbundleH = transEffH unbundleUnionH
{-# INLINE unbundleH #-}

{- | Expands effects at an @offset@ below the head of the list into a single
element using an open union.
-}
bundleUnderH
    :: forall offset bundle eh eh' ef
     . (BundleUnder UnionH offset eh eh' bundle)
    => Eff eh ef ~> Eff eh' ef
bundleUnderH = transEffH (bundleUnionUnderH @offset)
{-# INLINE bundleUnderH #-}

{- | Expands effects that have been bundled into an open union at an @offset@
below the head of the list.
-}
unbundleUnderH
    :: forall offset bundle eh eh' ef
     . (BundleUnder UnionH offset eh eh' bundle)
    => Eff eh' ef ~> Eff eh ef
unbundleUnderH = transEffH (unbundleUnionUnderH @offset)
{-# INLINE unbundleUnderH #-}

-- | Bundles all higher-order effects into a single open union.
bundleAllH :: Eff eh ef ~> Eff '[UnionH eh] ef
bundleAllH = transEffH bundleAllUnionH
{-# INLINE bundleAllH #-}

-- | Expands all higher-order effects from a single open union.
unbundleAllH :: Eff '[UnionH eh] ef ~> Eff eh ef
unbundleAllH = transEffH unbundleAllUnionH
{-# INLINE unbundleAllH #-}

-- ** Manipulating Tags & Keys

-- | Attaches the @tag@ to the first-order effect at the head of the list.
tag
    :: forall tag e ef eh
     . Eff eh (e ': ef) ~> Eff eh (e # tag ': ef)
tag = transform Tag
{-# INLINE tag #-}

-- | Removes the @tag@ from the tagged first-order effect at the head of the list.
untag
    :: forall tag e ef eh
     . Eff eh (e # tag ': ef) ~> Eff eh (e ': ef)
untag = transform unTag
{-# INLINE untag #-}

{- | Changes the @tag@ of the tagged first-order effect at the head of the list
to another tag @tag'@.
-}
retag
    :: forall tag' tag e ef eh
     . Eff eh (e # tag ': ef) ~> Eff eh (e # tag' ': ef)
retag = transform $ Tag . unTag
{-# INLINE retag #-}

-- | Attaches the @tag@ to the higher-order effect at the head of the list.
tagH
    :: forall tag e ef eh
     . (HFunctor e)
    => Eff (e ': eh) ef ~> Eff (e ## tag ': eh) ef
tagH = transformH TagH
{-# INLINE tagH #-}

{- | Removes the @tag@ from the tagged higher-order effect at the head of the
 list.
-}
untagH
    :: forall tag e eh ef
     . (HFunctor e)
    => Eff (e ## tag ': eh) ef ~> Eff (e ': eh) ef
untagH = transformH unTagH
{-# INLINE untagH #-}

{- | Changes the @tag@ of the tagged higher-order effect at the head of the list
to another tag @tag'@.
-}
retagH
    :: forall tag' tag e eh ef
     . (HFunctor e)
    => Eff (e ## tag ': eh) ef ~> Eff (e ## tag' ': eh) ef
retagH = transformH $ TagH . unTagH
{-# INLINE retagH #-}

-- | Removes the @key@ from the keyed first-order effect at the head of the list.
unkey
    :: forall key e ef eh
     . Eff eh (key #> e ': ef) ~> Eff eh (e ': ef)
unkey = transform unKey
{-# INLINE unkey #-}

{- | Changes the @key@ of the keyed first-order effect at the head of the list
to another key @key'@.
-}
rekey
    :: forall key' key e ef eh
     . Eff eh (key #> e ': ef) ~> Eff eh (key' #> e ': ef)
rekey = transform $ Key . unKey
{-# INLINE rekey #-}

-- | Removes the @key@ from the keyed higher-order effect at the head of the list.
unkeyH
    :: forall key e eh ef
     . (HFunctor e)
    => Eff (key ##> e ': eh) ef ~> Eff (e ': eh) ef
unkeyH = transformH unKeyH
{-# INLINE unkeyH #-}

{- | Changes the @key@ of the keyed higher-order effect at the head of the list
to another key @key'@.
-}
rekeyH
    :: forall key' key e eh ef
     . (HFunctor e)
    => Eff (key ##> e ': eh) ef ~> Eff (key' ##> e ': eh) ef
rekeyH = transformH $ KeyH . unKeyH
{-# INLINE rekeyH #-}