packages feed

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
  ]