packages feed

data-effects-core-0.4.2.0: src/Control/Effect/Transform.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE PatternSynonyms #-}

-- SPDX-License-Identifier: MPL-2.0

{- |
Copyright   :  (c) 2025 Sayo contributors
License     :  MPL-2.0 (see the file LICENSE)
Maintainer  :  ymdfield@outlook.jp
-}
module Control.Effect.Transform where

import Control.Effect (Eff (..), Free, hoist, sendFor, type (~>))
import Control.Effect.Interpret (interposeFor, interpret, reinterpret)
import Data.Effect.OpenUnion (
    Each,
    Has,
    In,
    KnownLength,
    KnownOrder,
    Membership,
    RemoveExps,
    RemoveHOEs,
    Suffix,
    SuffixUnder,
    Union,
    WeakenExps,
    WeakenHOEs,
    hfmapUnion,
    identityMembership,
    inject,
    keyMembership,
    labelMembership,
    mapUnion,
    prefixFor,
    prefixFor1,
    suffixFor,
    weaken,
    weakenExps,
    weakenFor,
    weakenHOEs,
    weakens,
    weakensUnder,
    (!:),
    pattern Here,
    type (++),
    type (:>),
 )
import Data.Effect.Tag (Tagged (Tag), unTag, type (#))

raise :: forall e es a ff c. (Free c ff) => Eff ff es a -> Eff ff (e ': es) a
raise = raises
{-# INLINE raise #-}

raises :: forall es es' a ff c. (Suffix es es', Free c ff) => Eff ff es a -> Eff ff es' a
raises = transAll weakens
{-# INLINE raises #-}

raiseUnder :: forall e0 e1 es a ff c. (Free c ff) => Eff ff (e0 ': es) a -> Eff ff (e0 ': e1 ': es) a
raiseUnder = raisesUnder
{-# INLINE raiseUnder #-}

raisesUnder :: forall e es es' a ff c. (Suffix es es', Free c ff) => Eff ff (e ': es) a -> Eff ff (e ': es') a
raisesUnder = transAll weakensUnder
{-# INLINE raisesUnder #-}

raisesUnders :: forall es es' a ff c. (SuffixUnder es es', Free c ff) => Eff ff es a -> Eff ff es' a
raisesUnders = transAll weakensUnder
{-# INLINE raisesUnders #-}

onlyFOEs :: forall es a ff c. (Free c ff, WeakenHOEs es) => Eff ff (RemoveHOEs es) a -> Eff ff es a
onlyFOEs = transAll weakenHOEs
{-# INLINE onlyFOEs #-}

onlyPolys :: forall es a ff c. (Free c ff, WeakenExps es) => Eff ff (RemoveExps es) a -> Eff ff es a
onlyPolys = transAll weakenExps
{-# INLINE onlyPolys #-}

raisePrefix
    :: forall es' es a ff c
     . (KnownLength es', Free c ff)
    => Eff ff es a
    -> Eff ff (es' ++ es) a
raisePrefix = transAll $ mapUnion $ prefixFor @es'
{-# INLINE raisePrefix #-}

raiseSuffix
    :: forall es' es a ff c
     . (Free c ff)
    => Eff ff es a
    -> Eff ff (es ++ es') a
raiseSuffix = transAll $ mapUnion $ suffixFor @es'
{-# INLINE raiseSuffix #-}

raisePrefix1
    :: forall fs x es a ff c
     . (KnownLength fs, Free c ff)
    => Eff ff es a
    -> Eff ff (Each fs x ++ es) a
raisePrefix1 = transAll $ mapUnion $ prefixFor1 @fs @x
{-# INLINE raisePrefix1 #-}

subsume :: forall e es a ff c. (e `In` es, Free c ff) => Eff ff (e ': es) a -> Eff ff es a
subsume = transAll $ inject identityMembership !: id
{-# INLINE subsume #-}

subsumeUnder :: forall e1 e0 es a ff c. (e1 `In` es, KnownOrder e0, Free c ff) => Eff ff (e0 ': e1 ': es) a -> Eff ff (e0 ': es) a
subsumeUnder = transAll $ inject Here !: inject (weakenFor identityMembership) !: weaken
{-# INLINE subsumeUnder #-}

-- todo: generalize subsume by type-class

transform
    :: forall e e' es a ff c
     . (KnownOrder e, KnownOrder e', Free c ff)
    => (e (Eff ff (e' ': es)) ~> e' (Eff ff (e' ': es)))
    -> Eff ff (e ': es) a
    -> Eff ff (e' ': es) a
transform f = reinterpret $ sendFor Here . f
{-# INLINE transform #-}

translate
    :: forall e e' es a ff c
     . (KnownOrder e, e' :> es, Free c ff)
    => (e (Eff ff es) ~> e' (Eff ff es))
    -> Eff ff (e ': es) a
    -> Eff ff es a
translate = translateFor labelMembership
{-# INLINE translate #-}

translateOn
    :: forall key e e' es a ff c
     . (KnownOrder e, Has key e' es, Free c ff)
    => (e (Eff ff es) ~> e' (Eff ff es))
    -> Eff ff (e ': es) a
    -> Eff ff es a
translateOn f = translateFor (keyMembership @key) (Tag . f)
{-# INLINE translateOn #-}

translateIn
    :: forall e e' es a ff c
     . (KnownOrder e, e' `In` es, Free c ff)
    => (e (Eff ff es) ~> e' (Eff ff es))
    -> Eff ff (e ': es) a
    -> Eff ff es a
translateIn = translateFor identityMembership
{-# INLINE translateIn #-}

translateFor
    :: forall e e' es a ff c
     . (KnownOrder e, KnownOrder e', Free c ff)
    => Membership e' es
    -> (e (Eff ff es) ~> e' (Eff ff es))
    -> Eff ff (e ': es) a
    -> Eff ff es a
translateFor i f = interpret $ sendFor i . f
{-# INLINE translateFor #-}

rewrite
    :: forall e es a ff c
     . (e :> es, Free c ff)
    => (e (Eff ff es) ~> e (Eff ff es))
    -> Eff ff es a
    -> Eff ff es a
rewrite = rewriteFor labelMembership
{-# INLINE rewrite #-}

rewriteOn
    :: forall key e es a ff c
     . (Has key e es, Free c ff)
    => (e (Eff ff es) ~> e (Eff ff es))
    -> Eff ff es a
    -> Eff ff es a
rewriteOn f = rewriteFor (keyMembership @key) (Tag . f . unTag)
{-# INLINE rewriteOn #-}

rewriteIn
    :: forall e es a ff c
     . (e `In` es, Free c ff)
    => (e (Eff ff es) ~> e (Eff ff es))
    -> Eff ff es a
    -> Eff ff es a
rewriteIn = rewriteFor identityMembership
{-# INLINE rewriteIn #-}

rewriteFor
    :: forall e es a ff c
     . (KnownOrder e, Free c ff)
    => Membership e es
    -> (e (Eff ff es) ~> e (Eff ff es))
    -> Eff ff es a
    -> Eff ff es a
rewriteFor i f = interposeFor i (sendFor i . f)
{-# INLINE rewriteFor #-}

transAll
    :: forall es es' ff a c
     . (Free c ff)
    => (Union es (Eff ff es') ~> Union es' (Eff ff es'))
    -> Eff ff es a
    -> Eff ff es' a
transAll f = go
  where
    go :: Eff ff es ~> Eff ff es'
    go (Eff a) = Eff $ hoist (f . hfmapUnion go) a
{-# INLINE transAll #-}

tag
    :: forall tag e es a ff c
     . (KnownOrder e, KnownOrder (e # tag), Free c ff)
    => Eff ff (e ': es) a
    -> Eff ff (e # tag ': es) a
tag = transform Tag
{-# INLINE tag #-}

untag
    :: forall tag e es a ff c
     . (KnownOrder e, KnownOrder (e # tag), Free c ff)
    => Eff ff (e # tag ': es) a
    -> Eff ff (e ': es) a
untag = transform unTag
{-# INLINE untag #-}