{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} module Handle where -- base import Control.Arrow import Data.Functor import Data.Functor.Identity import GHC.Natural (naturalToInteger) import GHC.TypeNats (KnownNat, Nat, natVal) -- transformers import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State.Strict -- test-framework import Test.Framework -- test-framework-quickcheck2 import Test.Framework.Providers.QuickCheck2 -- essence-of-live-coding import qualified Handle.LiveProgram import LiveCoding import Util -- One day replace State Int with Writer [String] testHandle :: Handle (State Int) String testHandle = Handle { create = do n <- get return $ "Handle #" ++ show n , destroy = const $ put 10000 } testUnitHandle :: Handle (State Int) () testUnitHandle = Handle { create = return () , destroy = const $ put 20000 } cellWithAction :: forall a b. State Int b -> Cell Identity a (String, Int) cellWithAction action = flip runStateC 0 $ runHandlingStateC $ handling testHandle >>> arrM (<$ lift action) testParametrisedHandle :: ParametrisedHandle Bool (State Int) String testParametrisedHandle = ParametrisedHandle {..} where createParametrised flag = do n <- get let greeting = if flag then "Ye Olde Handle No " else "Crazy new hdl #" return $ greeting ++ show n destroyParametrised = const $ const $ put 12345 changeParametrised = defaultChange createParametrised destroyParametrised cellWithActionParametrized :: forall a b. State Int b -> Cell Identity Bool (String, Int) cellWithActionParametrized action = flip runStateC 0 $ runHandlingStateC $ handlingParametrised testParametrisedHandle >>> arrM (<$ lift action) throwAfter2Steps :: Monad m => Cell (ExceptT () m) a Int throwAfter2Steps = arr (const 1) >>> sumC >>> throwIf_ (> 1) data Tag (tag :: Nat) = Tag deriving (Eq, Show) testTypelevelHandle :: KnownNat tag => Handle (State Int) (Tag tag) testTypelevelHandle = Handle { create = return Tag , destroy = put . fromInteger . naturalToInteger . natVal } cellWithActionTypelevel :: KnownNat tag => State Int b -> Cell Identity a (Tag tag, Int) cellWithActionTypelevel action = flip runStateC 0 $ runHandlingStateC $ handling testTypelevelHandle >>> arrM (<$ lift action) test = testGroup "Handle" [ testProperty "Preserve Handles" CellMigrationSimulation { cell1 = cellWithAction $ modify (+ 1) , cell2 = cellWithAction $ return () , input1 = replicate 3 () , input2 = replicate 3 () , output1 = ("Handle #0",) <$> [1, 2, 3] , output2 = ("Handle #0",) <$> [3, 3, 3] } , testProperty "Initialise Handles upon migration" CellMigrationSimulation { cell1 = flip runStateC 0 $ constM $ modify (+ 1) >> return "" , cell2 = cellWithAction $ return () , input1 = replicate 3 () , input2 = replicate 3 () , output1 = ("",) <$> [1, 2, 3] , output2 = ("Handle #3",) <$> [3, 3, 3] } , testProperty "Preserve Handles in more complex migration" CellMigrationSimulation { cell1 = flip runStateC 22 $ constM (modify (+ 1)) >>> runHandlingStateC (handling testHandle) , cell2 = cellWithAction $ return () , input1 = replicate 3 () , input2 = replicate 3 () , output1 = ("Handle #23",) <$> [23, 24, 25] , output2 = ("Handle #23",) <$> replicate 3 25 } , testProperty "Reinitialise Handles in too complex migration" CellMigrationSimulation { cell1 = flip runStateC 22 $ constM (modify (+ 1)) >>> constM get >>> sumC >>> sumC >>> runHandlingStateC (handling testHandle) , cell2 = cellWithAction $ return () , input1 = replicate 3 () , input2 = replicate 3 () , output1 = ("Handle #23",) <$> [23, 24, 25] , output2 = ("Handle #25",) <$> replicate 3 25 } , testProperty "Doesn't crash when handle is introspected by migration" CellMigrationSimulation { cell1 = cellWithAction $ return () , cell2 = flip runStateC 0 $ runHandlingStateC $ handling testUnitHandle >>> arr (const "") , input1 = replicate 3 () , input2 = replicate 3 () , output1 = ("Handle #0",) <$> replicate 3 0 , output2 = ("",) <$> replicate 3 10000 } , testProperty "Trigger destructors" CellMigrationSimulation { cell1 = cellWithAction $ return () , cell2 = flip runStateC 23 $ runHandlingStateC $ arr $ const "Done" , input1 = replicate 3 () , input2 = replicate 3 () , output1 = ("Handle #0",) <$> replicate 3 0 , output2 = ("Done",) <$> replicate 3 10000 } , testProperty "Changing parameters triggers destructors" CellSimulation { cell = cellWithActionParametrized $ modify (+ 1) , input = [True, True, False, False] , output = [ ("Ye Olde Handle No 0", 1) , ("Ye Olde Handle No 0", 2) , ("Crazy new hdl #12345", 12346) , ("Crazy new hdl #12345", 12347) ] } , testProperty "Transient control flow does not trigger destructors or constructors" CellSimulation { cell = cellWithAction (modify (+ 1)) ||| arr (const ("Nope", 23)) , input = [Right (), Left (), Left (), Right (), Left ()] , output = [ ("Nope", 23) , ("Handle #0", 1) , ("Handle #0", 2) , ("Nope", 23) , ("Handle #0", 3) ] } , testProperty "Permanent control flow does not trigger destructors or constructors" CellSimulation { cell = safely $ do void $ try $ throwAfter2Steps >>> arr (const ("Nope", 23)) void $ try $ throwAfter2Steps >>> liftCell (cellWithAction (modify (+ 1))) safe $ arr $ const ("Nope", 23) , input = replicate 5 () , output = [ ("Nope", 23) , ("Nope", 23) , ("Handle #0", 1) , ("Handle #0", 2) , ("Nope", 23) ] } , testProperty "Change of type level tags trigger destructors" CellMigrationSimulation { cell1 = (cellWithActionTypelevel @23000 $ modify (+ 1)) >>> arr snd , cell2 = (cellWithActionTypelevel @42000 $ modify (+ 2)) >>> arr snd , input1 = replicate 3 () , input2 = replicate 3 () , output1 = [1, 2, 3] , output2 = [23000, 23002, 23004] } , Handle.LiveProgram.test ]