module Main (main) where import Data.Function (on) import Data.List (intercalate) import Control.Monad ((>=>)) import Control.Monad.Trans.Writer (Writer, runWriter, tell) import Test.QuickCheck (Gen, Arbitrary(..), choose) import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.QuickCheck2 (testProperty) import Pipes import Pipes.Core import Prelude hiding (log) main :: IO () main = defaultMain tests tests :: [Test] tests = [ testGroup "Kleisli Category" $ testCategory (>=>) return , testGroup "Respond Category" $ testCategory (/>/) respond ++ [ testProperty "Distributivity" prop_respond_Distributivity ] , testGroup "Request Category" $ testCategory (\>\) request ++ [ testProperty "Distributivity" prop_request_Distributivity , testProperty "Zero Law" prop_request_ZeroLaw ] , testGroup "Pull Category" $ testCategory (>+>) pull , testGroup "Push Category" $ testCategory (>~>) push , testGroup "Push/Pull" [ testProperty "Associativity" prop_pushPull_Associativity ] , testGroup "Duals" [ testGroup "Request" [ testProperty "Composition" prop_dual_RequestComposition , testProperty "Identity" prop_dual_RequestIdentity ] , testGroup "Respond" [ testProperty "Composition" prop_dual_RespondComposition , testProperty "Identity" prop_dual_RespondIdentity ] , testProperty "Distributivity" prop_dual_ReflectDistributivity , testProperty "Zero Law" prop_dual_ReflectZeroLaw , testProperty "Involution" prop_dual_Involution ] , testGroup "Functor Laws" [ testProperty "Identity" prop_FunctorIdentity ] ] arbitraryBoundedEnum' :: (Bounded a, Enum a) => Gen a arbitraryBoundedEnum' = do let mn = minBound mx = maxBound `asTypeOf` mn n <- choose (fromEnum mn, fromEnum mx) return (toEnum n `asTypeOf` mn) data ClientStep = ClientRequest | ClientLog | ClientInc deriving (Enum, Bounded) instance Arbitrary ClientStep where arbitrary = arbitraryBoundedEnum' shrink _ = [] instance Show ClientStep where show x = case x of ClientRequest -> "request" ClientLog -> "log" ClientInc -> "inc" data ServerStep = ServerRespond | ServerLog | ServerInc deriving (Enum, Bounded) instance Arbitrary ServerStep where arbitrary = arbitraryBoundedEnum' shrink _ = [] instance Show ServerStep where show x = case x of ServerRespond -> "respond" ServerLog -> "log" ServerInc -> "inc" data ProxyStep = ProxyRequest | ProxyRespond | ProxyLog | ProxyInc deriving (Enum, Bounded) instance Arbitrary ProxyStep where arbitrary = arbitraryBoundedEnum' shrink _ = [] instance Show ProxyStep where show x = case x of ProxyRequest -> "request" ProxyRespond -> "respond" ProxyLog -> "log" ProxyInc -> "inc" log :: Int -> Proxy a' a b' b (Writer [Int]) Int log n = do lift (tell [n]) return n inc :: (Monad m) => Int -> Proxy a' a b' b m Int inc n = return (n + 1) correct :: String -> String correct str = case str of [] -> "return" _ -> str newtype AClient = AClient { unAClient :: [ClientStep] } instance Arbitrary AClient where arbitrary = fmap AClient arbitrary shrink = map AClient . shrink . unAClient instance Show AClient where show = correct . intercalate " >=> " . map show . unAClient aClient :: AClient -> Int -> Client Int Int (Writer [Int]) Int aClient = foldr (>=>) return . map f . unAClient where f x = case x of ClientRequest -> request ClientLog -> log ClientInc -> inc newtype AServer = AServer { unAServer :: [ServerStep] } instance Arbitrary AServer where arbitrary = fmap AServer arbitrary shrink = map AServer . shrink . unAServer instance Show AServer where show = correct . intercalate " >=> " . map show . unAServer aServer :: AServer -> Int -> Server Int Int (Writer [Int]) Int aServer = foldr (>=>) return . map f . unAServer where f x = case x of ServerRespond -> respond ServerLog -> log ServerInc -> inc newtype AProxy = AProxy { unAProxy :: [ProxyStep] } instance Arbitrary AProxy where arbitrary = fmap AProxy arbitrary shrink = map AProxy . shrink . unAProxy instance Show AProxy where show = correct . intercalate " >=> " . map show . unAProxy aProxy :: AProxy -> Int -> Proxy Int Int Int Int (Writer [Int]) Int aProxy = foldr (>=>) return . map f . unAProxy where f x = case x of ProxyRequest -> request ProxyRespond -> respond ProxyLog -> log ProxyInc -> inc type ProxyK = Int -> Proxy Int Int Int Int (Writer [Int]) Int type Operation = ProxyK -> ProxyK -> ProxyK infix 0 === (===) :: ProxyK -> ProxyK -> AServer -> AClient -> Bool (===) pl pr p0 p1 = let sv = aServer p0 cl = aClient p1 f p = runWriter (runEffect (p 0)) in on (==) f (sv >+> pl >+> cl) (sv >+> pr >+> cl) gen_prop_RightIdentity, gen_prop_LeftIdentity :: Operation -> ProxyK -- right/left identity element -> AProxy -> AServer -> AClient -> Bool gen_prop_RightIdentity (>>>) idt f' = let f = aProxy f' in (f >>> idt) === f gen_prop_LeftIdentity (>>>) idt f' = let f = aProxy f' in (idt >>> f) === f gen_prop_Associativity :: Operation -> AProxy -> AProxy -> AProxy -> AServer -> AClient -> Bool gen_prop_Associativity (>>>) f' g' h' = let f = aProxy f' g = aProxy g' h = aProxy h' in f >>> (g >>> h) === (f >>> g) >>> h testCategory :: Operation -> ProxyK -> [Test] testCategory op idt = [ testProperty "Left Identity" $ gen_prop_LeftIdentity op idt , testProperty "Right Identity" $ gen_prop_RightIdentity op idt , testProperty "Associativity" $ gen_prop_Associativity op ] -- Respond Category prop_respond_Distributivity f' g' h' = let f = aProxy f' g = aProxy g' h = aProxy h' in (f >=> g) />/ h === (f />/ h) >=> (g />/ h) -- Request Category prop_request_Distributivity f' g' h' = let f = aProxy f' g = aProxy g' h = aProxy h' in f \>\ (g >=> h) === (f \>\ g) >=> (f \>\ h) prop_request_ZeroLaw f' = let f = aProxy f' in (f \>\ return) === return -- Push/Pull prop_pushPull_Associativity f' g' h' = let f = aProxy f' g = aProxy g' h = aProxy h' in (f >+> g) >~> h === f >+> (g >~> h) -- Duals prop_dual_RequestComposition f' g' = let f = aProxy f' g = aProxy g' in reflect . (f \>\ g) === reflect . g />/ reflect . f prop_dual_RequestIdentity = reflect . request === respond prop_dual_RespondComposition f' g' = let f = aProxy f' g = aProxy g' in reflect . (f />/ g) === reflect . g \>\ reflect . f prop_dual_RespondIdentity = reflect . respond === request prop_dual_ReflectDistributivity f' g' = let f = aProxy f' g = aProxy g' in reflect . (f >=> g) === reflect . f >=> reflect . g prop_dual_ReflectZeroLaw = reflect . return === return prop_dual_Involution f' = let f = aProxy f' in (reflect . reflect) . f >=> return === f -- Functor Laws prop_FunctorIdentity p' = let p = aProxy p' in fmap id p === id p