polysemy-resume-0.7.0.0: test/Polysemy/Resume/Test/ScopedTest.hs
{-# options_ghc -fplugin=Polysemy.Plugin #-}
module Polysemy.Resume.Test.ScopedTest where
import Control.Concurrent.STM (TVar, atomically, modifyTVar', newTVarIO, readTVarIO, writeTVar)
import Polysemy.Scoped (
Scoped,
interpretScoped,
interpretScopedH,
interpretScopedH',
interpretScopedWithH,
rescope,
scoped,
scoped_,
)
import Polysemy.Test (UnitTest, runTestAuto, unitTest, (===))
import Test.Tasty (TestTree, testGroup)
import Polysemy.Resume.Effect.Resumable (type (!!))
import Polysemy.Resume.Effect.Stop (Stop, stop)
import Polysemy.Resume.Interpreter.Resumable (interpretResumableH)
import Polysemy.Resume.Interpreter.Scoped (
interpretResumableScopedH,
interpretResumableScopedWithH,
interpretScopedRH,
interpretScopedRWithH,
interpretScopedResumableH,
interpretScopedResumableWithH,
)
import Polysemy.Resume.Resume (resumeAs, resuming, (!!))
newtype Par =
Par { unPar :: Int }
deriving stock (Eq, Show)
deriving newtype (Num, Real, Enum, Integral, Ord)
data E :: Effect where
E1 :: E m Int
E2 :: E m Int
makeSem ''E
data F :: Effect where
F :: F m Int
makeSem ''F
handleE ::
Member (Embed IO) r =>
TVar Int ->
E m a ->
Tactical effect m (F : r) a
handleE tv = \case
E1 -> do
i1 <- embed (readTVarIO tv)
i2 <- f
pureT (i1 + i2 + 10)
E2 ->
pureT (-1)
interpretF ::
Member (Embed IO) r =>
TVar Int ->
InterpreterFor F r
interpretF tv =
interpret \ F -> do
embed (atomically (writeTVar tv 7))
pure 5
scope ::
Member (Embed IO) r =>
Par ->
(TVar Int -> Sem (F : r) a) ->
Sem r a
scope (Par n) use = do
tv <- embed (newTVarIO n)
interpretF tv (use tv)
test_scopedWith :: UnitTest
test_scopedWith =
runTestAuto $ interpretScopedWithH @'[F] @_ @_ @E scope handleE do
(i1, i2) <- scoped @_ @E 20 do
i1 <- e1
i2 <- scoped @_ @E 23 e1
pure (i1, i2)
35 === i1
38 === i2
data H1 :: Effect where
H1a :: m Int -> H1 m Int
H1b :: H1 m Int
H1c :: H1 m Int
H1d :: m Int -> H1 m Int
makeSem ''H1
scopeH1 :: Int -> (Int -> Sem (Stop Int : r) a) -> Sem (Stop Int : r) a
scopeH1 p use =
if p == (-1) then stop 500000 else use (p + 5)
handleH1 :: Int -> H1 m a -> Tactical H1 m (Stop Int : r) a
handleH1 n = \case
H1a ma -> do
i <- runTSimple ma
pure (i <&> \ i' -> i' + n + 1)
H1b ->
pureT 50000
H1c ->
stop 100000
H1d ma ->
raise . interpretH (handleH1 (n + 1)) =<< runT ma
test_scopedResumable :: UnitTest
test_scopedResumable =
runTestAuto $ interpretScopedResumableH @Int @Int @H1 @Int scopeH1 handleH1 do
(i1, i2, i3, i4, i5) <- resumeAs @Int @(Scoped Int H1) (-50, -100, -200, -500, -700) $ scoped @Int @H1 20 do
i1 <- h1a do
h1b
i2 <- resumeAs @Int @(Scoped Int H1) (-1000) $ scoped @Int @H1 23 do
h1a do
h1b
i3 <- resuming pure $ scoped @Int @H1 5000 do
h1a do
h1c
i4 <- resuming pure $ scoped @Int @H1 (-1) do
h1b
i5 <- h1d do
h1a h1b
pure (i1, i2, i3, i4, i5)
50026 === i1
50029 === i2
100000 === i3
500000 === i4
50027 === i5
handleRE ::
Member (Embed IO) r =>
TVar Int ->
E m a ->
Tactical effect m (F : Stop Int : r) a
handleRE tv = \case
E1 -> do
i1 <- embed (readTVarIO tv)
i2 <- f
_ <- stop (i1 + i2)
pureT (i1 + i2 + 10)
E2 ->
pureT =<< f
scopeR ::
Member (Embed IO) r =>
Par ->
(TVar Int -> Sem (F : Stop Int : r) a) ->
Sem (Stop Int : r) a
scopeR (Par n) use = do
tv <- embed (newTVarIO n)
_ <- interpretF tv (use tv)
stop . (50 +) =<< embed (readTVarIO tv)
test_scopedResumableWith :: UnitTest
test_scopedResumableWith =
runTestAuto $ interpretScopedResumableWithH @'[F] scopeR handleRE do
i1 <- scoped 20 e1 !! pure
i2 <- scoped 23 e2 !! pure
25 === i1
57 === i2
data Rs :: Effect where
Rs1 :: m Int -> Rs m Int
Rs2 :: Rs m Int
Rs3 :: m Int -> Rs m Int
Rs4 :: Rs m Int
makeSem ''Rs
scopeRs :: Int -> (Int -> Sem r a) -> Sem r a
scopeRs n use =
use (n + 1)
handleRs :: Int -> Rs m a -> Tactical (Rs !! Int) m (Stop Int : r) a
handleRs n = \case
Rs1 ma -> raise . interpretResumableH (handleRs (n + 1000000)) =<< runT ma
Rs2 -> pureT (n + 20)
Rs3 _ -> stop 200
Rs4 -> stop 2000
test_resumableScoped :: UnitTest
test_resumableScoped =
runTestAuto $ interpretResumableScopedH @Int @Int @Rs @Int scopeRs handleRs do
(i1, i2, i3, i4, i5) <- scoped 10000 do
i1 <- resuming pure $ rs1 do
rs2
i2 <- resuming pure $ rs3 do
rs4
i3 <- rs2 !! pure
i4 <- resuming pure $ rs1 do
rs4
i5 <- raise $ scoped @Int @(Rs !! Int) 100000 do
resumeAs 1000 $ rs1 rs2
pure (i1, i2, i3, i4, i5)
1010021 === i1
200 === i2
10021 === i3
2000 === i4
1100021 === i5
data Rsw :: Effect where
Rsw1 :: m Int -> Rsw m Int
Rsw2 :: Rsw m Int
Rsw3 :: m Int -> Rsw m Int
Rsw4 :: Rsw m Int
makeSem ''Rsw
data RswExtra :: Effect where
RswExtra :: RswExtra m Int
makeSem ''RswExtra
scopeRsw :: Int -> (Int -> Sem (RswExtra : r) a) -> Sem r a
scopeRsw n use =
interpret (\ RswExtra -> pure 20) $
use (n + 1)
handleRsw ::
Member RswExtra r =>
Int ->
Rsw m a ->
Tactical (Rsw !! Int) m (Stop Int : r) a
handleRsw n = \case
Rsw1 ma -> raise . interpretResumableH (handleRsw (n + 1000000)) =<< runT ma
Rsw2 -> pureT . (n +) =<< rswExtra
Rsw3 _ -> stop 200
Rsw4 -> stop 2000
test_resumableScopedWith :: UnitTest
test_resumableScopedWith =
runTestAuto $ interpretResumableScopedWithH @'[RswExtra] scopeRsw handleRsw do
(i1, i2, i3, i4, i5) <- scoped 10000 do
i1 <- resuming pure $ rsw1 do
rsw2
i2 <- resuming pure $ rsw3 do
rsw4
i3 <- rsw2 !! pure
i4 <- resuming pure $ rsw1 do
rsw4
i5 <- raise $ scoped @Int @(Rsw !! Int) 100000 do
resumeAs 1000 $ rsw1 rsw2
pure (i1, i2, i3, i4, i5)
1010021 === i1
200 === i2
10021 === i3
2000 === i4
1100021 === i5
data Rsr :: Effect where
Rsr1 :: m Int -> Rsr m Int
Rsr2 :: Rsr m Int
Rsr3 :: m Int -> Rsr m Int
Rsr4 :: Rsr m Int
makeSem ''Rsr
scopeRsr :: Int -> (Int -> Sem (Stop Double : r) a) -> Sem (Stop Double : r) a
scopeRsr n use =
use (n + 1)
handleRsr :: Int -> Rsr m a -> Tactical (Rsr !! Int) m (Stop Int : r) a
handleRsr n = \case
Rsr1 ma -> raise . interpretResumableH (handleRsr (n + 1000000)) =<< runT ma
Rsr2 -> pureT (n + 20)
Rsr3 _ -> stop 200
Rsr4 -> stop 2000
test_scopedR :: UnitTest
test_scopedR =
runTestAuto $ interpretScopedRH @Int @Int @Rsr @Double @Int scopeRsr handleRsr do
(i1, i2, i3, i4, i5) <- resuming (\ i -> pure (round i, round i, round i, round i, round i)) $ scoped 10000 do
i1 <- resuming pure $ rsr1 do
rsr2
i2 <- resuming pure $ rsr3 do
rsr4
i3 <- rsr2 !! pure
i4 <- resuming pure $ rsr1 do
rsr4
i5 <- raise $ scoped @Int @(Rsr !! Int) 100000 do
resumeAs 1000 $ rsr1 rsr2
pure (i1, i2, i3, i4, i5)
1010021 === i1
200 === i2
10021 === i3
2000 === i4
1100021 === i5
scopeRsrw :: Int -> (Int -> Sem (RswExtra : Stop Double : r) a) -> Sem (Stop Double : r) a
scopeRsrw n use =
interpret (\ RswExtra -> pure 20) $
use (n + 1)
handleRsrw :: Int -> Rsr m a -> Tactical (Rsr !! Int) m (Stop Int : r) a
handleRsrw n = \case
Rsr1 ma -> raise . interpretResumableH (handleRsr (n + 1000000)) =<< runT ma
Rsr2 -> pureT (n + 20)
Rsr3 _ -> stop 200
Rsr4 -> stop 2000
test_scopedRWith :: UnitTest
test_scopedRWith =
runTestAuto $ interpretScopedRWithH @'[RswExtra] scopeRsrw handleRsrw do
(i1, i2, i3, i4, i5) <- resuming (\ i -> pure (round i, round i, round i, round i, round i)) $ scoped 10000 do
i1 <- resuming pure $ rsr1 do
rsr2
i2 <- resuming pure $ rsr3 do
rsr4
i3 <- rsr2 !! pure
i4 <- resuming pure $ rsr1 do
rsr4
i5 <- raise $ scoped @Int @(Rsr !! Int) 100000 do
resumeAs 1000 $ rsr1 rsr2
pure (i1, i2, i3, i4, i5)
1010021 === i1
200 === i2
10021 === i3
2000 === i4
1100021 === i5
scopeH ::
Member (Embed IO) r =>
Par ->
(TVar Int -> Tactical e m r a) ->
Tactical e m r a
scopeH (Par n) use = do
tv <- embed (newTVarIO n)
use tv
handleH ::
Member (Embed IO) r =>
TVar Int ->
E (Sem r0) a ->
Tactical e (Sem r0) r a
handleH tv = \case
E1 -> do
embed (atomically (modifyTVar' tv (+1)))
pureT =<< embed (readTVarIO tv)
E2 ->
pureT 23
test_scopedH' :: UnitTest
test_scopedH' =
runTestAuto $ interpretScopedH' scopeH handleH do
r <- scoped @_ @E 100 do
i1 <- e1
i2 <- scoped @_ @E 200 e1
i3 <- e1
pure (i1, i2, i3)
(101, 201, 102) === r
data RPar =
RPar {
rpD :: Double,
rpB :: Bool
}
deriving stock (Eq, Show)
data RRes =
RRes {
rrD :: Double,
rrI :: Int
}
deriving stock (Eq, Show)
scopeR1 :: RPar -> (RRes -> Sem r a) -> Sem r a
scopeR1 (RPar d b) use =
use (RRes (d * 2) (if b then 1 else 2))
handleR1 :: RRes -> E m x -> Sem r x
handleR1 (RRes d i) = \case
E1 -> pure (i + floor (d * 3))
E2 -> pure (i + floor (d * 4))
rescopeP :: Int -> RPar
rescopeP i =
RPar (fromIntegral i) False
test_rescope :: UnitTest
test_rescope =
runTestAuto $ interpretScoped scopeR1 handleR1 do
r <- rescope rescopeP do
scoped @_ @E 100 do
e1
602 === r
data HO :: Effect where
Inc :: m a -> HO m a
Ret :: HO m Int
makeSem ''HO
scopeHO :: () -> (() -> Sem r a) -> Sem r a
scopeHO () use =
use ()
handleHO :: Int -> () -> HO m a -> Tactical HO m r a
handleHO n () = \case
Inc ma -> raise . interpretH (handleHO (n + 1) ()) =<< runT ma
Ret -> pureT n
test_higherOrder :: UnitTest
test_higherOrder =
runTestAuto $ interpretScopedH scopeHO (handleHO 1) do
r <- scoped_ @HO do
inc do
ret
2 === r
data Esc :: Effect where
Esc :: Esc m Int
makeSem ''Esc
data Indirect :: Effect where
Indirect :: Indirect m Int
makeSem ''Indirect
interpretIndirect :: Member Esc r => InterpreterFor Indirect r
interpretIndirect = interpret \ Indirect -> esc
handleEsc :: Int -> Esc m a -> Sem r a
handleEsc i = \ Esc -> pure i
test_escape :: UnitTest
test_escape =
runTestAuto $ interpretScoped (flip ($)) handleEsc $ scoped @_ @Esc 2 $ interpretIndirect do
r <- scoped @_ @Esc 1 indirect
2 === r
test_scoped :: TestTree
test_scoped =
testGroup "scoped" [
unitTest "scopedWith" test_scopedWith,
unitTest "scopedResumable" test_scopedResumable,
unitTest "scopedResumableWith" test_scopedResumableWith,
unitTest "resumableScoped" test_resumableScoped,
unitTest "resumableScopedWith" test_resumableScopedWith,
unitTest "scopedR" test_scopedR,
unitTest "scopedRWith" test_scopedRWith,
unitTest "scopedH'" test_scopedH',
unitTest "rescope" test_rescope,
unitTest "switch higher-order interpreter" test_higherOrder
-- unitTest "nested scope with other interpreter in between" test_escape
]