{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators, DataKinds #-} {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-unused-do-bind #-} module Control.Eff.Coroutine.Test (testGroups) where import Test.HUnit hiding (State) import Control.Eff import Control.Eff.Coroutine import Control.Eff.Trace import Control.Eff.Reader.Strict import Utils import Test.Framework.TH import Test.Framework.Providers.HUnit testGroups = [ $(testGroupGenerator) ] yieldInt :: Member (Yield Int ()) r => Int -> Eff r () yieldInt = yield case_Coroutines_c1 :: Assertion case_Coroutines_c1 = do ((), actual) <- catchOutput c1 assertEqual "Coroutine: Simple coroutines using Eff" (unlines ["1", "2", "Done"]) actual where th1 :: Member (Yield Int ()) r => Eff r () th1 = yieldInt 1 >> yieldInt 2 c1 = runTrace (loop =<< runC th1) where loop (Y x k) = trace (show (x::Int)) >> k () >>= loop loop (Done) = trace ("Done") case_Coroutines_c2 :: Assertion case_Coroutines_c2 = do ((), actual1) <- catchOutput c2 assertEqual "Coroutine: Add dynamic variables" (unlines ["10", "10", "Done"]) actual1 ((), actual2) <- catchOutput c21 assertEqual "Coroutine: locally changing the dynamic environment for the suspension" (unlines ["10", "11", "Done"]) actual2 where -- The code is essentially the same as that in transf.hs (only added -- a type specializtion on yield). The inferred signature is different though. -- Before it was -- th2 :: MonadReader Int m => CoT Int m () -- Now it is more general: th2 :: (Member (Yield Int ()) r, Member (Reader Int) r) => Eff r () th2 = ask >>= yieldInt >> (ask >>= yieldInt) -- Code is essentially the same as in transf.hs; no liftIO though c2 = runTrace $ runReader (10::Int) (loop =<< runC th2) where loop (Y x k) = trace (show (x::Int)) >> k () >>= loop loop Done = trace "Done" -- locally changing the dynamic environment for the suspension c21 = runTrace $ runReader (10::Int) (loop =<< runC th2) where loop (Y x k) = trace (show (x::Int)) >> local (+(1::Int)) (k ()) >>= loop loop Done = trace "Done" case_Coroutines_c3 :: Assertion case_Coroutines_c3 = do ((), actual1) <- catchOutput c3 assertEqual "Coroutine: two sorts of local rebinding" (unlines ["10", "10", "20", "20", "Done"]) actual1 ((), actual2) <- catchOutput c31 let expected2 = (unlines ["10", "11", "21", "21", "Done"]) assertEqual "Coroutine: locally changing the dynamic environment for the suspension" expected2 actual2 ((), actual3) <- catchOutput c4 assertEqual "Coroutine: abstracting the client computation" expected2 actual3 where th3 :: (Member (Yield Int ()) r, Member (Reader Int) r) => Eff r () th3 = ay >> ay >> local (+(10::Int)) (ay >> ay) where ay = ask >>= yieldInt c3 = runTrace $ runReader (10::Int) (loop =<< runC th3) where loop (Y x k) = trace (show (x::Int)) >> k () >>= loop loop Done = trace "Done" -- The desired result: the coroutine shares the dynamic environment with its -- parent; however, when the environment is locally rebound, it becomes -- private to coroutine. c31 = runTrace $ runReader (10::Int) (loop =<< runC th3) where loop (Y x k) = trace (show (x::Int)) >> local (+(1::Int)) (k ()) >>= loop loop Done = trace "Done" -- We now make explicit that the client computation, run by th4, -- is abstract. We abstract it out of th4 c4 = runTrace $ runReader (10::Int) (loop =<< runC (th4 client)) where loop (Y x k) = trace (show (x::Int)) >> local (+(1::Int)) (k ()) >>= loop loop Done = trace "Done" -- cl, client, ay are monomorphic bindings th4 cl = cl >> local (+(10::Int)) cl client = ay >> ay ay = ask >>= yieldInt case_Corountines_c5 :: Assertion case_Corountines_c5 = do ((), actual) <- catchOutput c5 let expected = unlines ["10" ,"11" ,"12" ,"18" ,"18" ,"18" ,"29" ,"29" ,"29" ,"29" ,"29" ,"29" ,"Done" ] assertEqual "Corountine: Even more dynamic example" expected actual where c5 = runTrace $ runReader (10::Int) (loop =<< runC (th client)) where loop (Y x k) = trace (show (x::Int)) >> local (\_y->x+1) (k ()) >>= loop loop Done = trace "Done" -- cl, client, ay are monomorphic bindings client = ay >> ay >> ay ay = ask >>= yieldInt -- There is no polymorphic recursion here th cl = do cl v <- ask (if v > (20::Int) then id else local (+(5::Int))) cl if v > (20::Int) then return () else local (+(10::Int)) (th cl) case_Coroutines_c7 :: Assertion case_Coroutines_c7 = do ((), actual) <- catchOutput c7 let expected = unlines ["1010" ,"1021" ,"1032" ,"1048" ,"1064" ,"1080" ,"1101" ,"1122" ,"1143" ,"1169" ,"1195" ,"1221" ,"1252" ,"1283" ,"1314" ,"1345" ,"1376" ,"1407" ,"Done" ] assertEqual "Coroutine: And even more dynamic example" expected actual where c7 = runTrace $ runReader (1000::Double) (runReader (10::Int) (loop =<< runC (th client))) where loop (Y x k) = trace (show (x::Int)) >> local (\_y->fromIntegral (x+1)::Double) (k ()) >>= loop loop Done = trace "Done" -- cl, client, ay are monomorphic bindings client = ay >> ay >> ay ay = ask >>= \x -> ask >>= \y -> yieldInt (x + round (y::Double)) -- There is no polymorphic recursion here th cl = do cl v <- ask (if v > (20::Int) then id else local (+(5::Int))) cl if v > (20::Int) then return () else local (+(10::Int)) (th cl) case_Coroutines_c7' :: Assertion case_Coroutines_c7' = do ((), actual) <- catchOutput c7' let expected = unlines ["1010" ,"1021" ,"1032" ,"1048" ,"1048" ,"1048" ,"1069" ,"1090" ,"1111" ,"1137" ,"1137" ,"1137" ,"1168" ,"1199" ,"1230" ,"1261" ,"1292" ,"1323" ,"Done" ] assertEqual "Coroutine: And even more dynamic example" expected actual where c7' = runTrace $ runReader (1000::Double) (runReader (10::Int) (loop =<< runC (th client))) where loop (Y x k) = trace (show (x::Int)) >> local (\_y->fromIntegral (x+1)::Double) (k ()) >>= loop loop Done = trace "Done" -- cl, client, ay are monomorphic bindings client = ay >> ay >> ay ay = ask >>= \x -> ask >>= \y -> yieldInt (x + round (y::Double)) -- There is no polymorphic recursion here th cl = do cl v <- ask (if v > (20::Int) then id else local (+(5::Double))) cl if v > (20::Int) then return () else local (+(10::Int)) (th cl)