{-# LANGUAGE DataKinds, OverloadedStrings, TemplateHaskell #-} module Main where import Control.Arrow ( left ) import Control.Concurrent ( threadDelay ) import Control.ParDual.Class import Control.Monad ( unless ) import Data.Bitraversable ( bitraverse ) import Data.Foldable ( traverse_ ) import Data.IORef import Data.Validation ( fromEither , toEither ) import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Refined import System.Exit ( exitFailure ) main :: IO () main = do results <- sequence [checkParallel dualTests] unless (and results) exitFailure dualTests :: Group dualTests = $$(discover) prop_parMap2_on_success :: Property prop_parMap2_on_success = property $ do a <- forAll $ Gen.int (Range.linear 18 100) n <- forAll $ Gen.list (Range.linear 1 50) Gen.alpha let result = parMap2 (ref a) (ref n) Person expected = Person <$> ref a <*> ref n result === expected prop_parMap2_accumulates_errors :: Property prop_parMap2_accumulates_errors = property $ do a <- forAll $ Gen.int (Range.linear 0 17) n <- forAll $ Gen.list (Range.linear 0 0) Gen.alpha let res1 = parMap2 (ref a) (ref n) Person res2 = Person <$> ref a <*> ref n exp1 = Left [ "The predicate (GreaterThan 17) does not hold: \n Value is not greater than 17" , "The predicate (SizeGreaterThan 0) does not hold: \n Size of Foldable is not greater than 0\n Size is: 0" ] exp2 = left (take 1) exp1 res1 === exp1 res2 === exp2 prop_parTraverse_accumulates_errors :: Property prop_parTraverse_accumulates_errors = property $ do xs <- forAll $ Gen.list (Range.linear 1 10) (Gen.int (Range.linear 1 10)) let f :: Int -> Either [String] Int f n = Left [show n] res1 = parTraverse f xs res2 = traverse f xs exp1 = Left (show <$> xs) exp2 = Left (take 1 $ show <$> xs) res1 === exp1 res2 === exp2 -- This one is tricky to test but this seems good enough for now prop_parTraverse_io_is_concurrent :: Property prop_parTraverse_io_is_concurrent = withTests (10 :: TestLimit) $ property $ do xs <- forAll $ Gen.list (Range.linear 15 25) (Gen.int (Range.linear 1 10)) let f r n = threadDelay (1 * 3000) >> atomicModifyIORef r (\x -> (n : x, n)) ref1 <- evalIO $ newIORef [] :: PropertyT IO (IORef [Int]) ref2 <- evalIO $ newIORef [] :: PropertyT IO (IORef [Int]) evalIO $ parTraverse_ (f ref1) xs evalIO $ traverse_ (f ref2) xs res1 <- evalIO $ readIORef ref1 res2 <- evalIO $ readIORef ref2 -- Avoid cases where all elements are the same (could prob. be done in a better way) let exp1 = if and (fmap (== head xs) (tail xs)) then [] else reverse xs res1 /== exp1 res2 === reverse xs prop_parMap2_on_lists :: Property prop_parMap2_on_lists = property $ do xs <- forAll $ Gen.constant [1 .. 5] :: PropertyT IO [Int] ys <- forAll $ Gen.constant [6 .. 10] :: PropertyT IO [Int] let res1 = parMap2 xs ys (+) res2 = (+) <$> xs <*> ys exp1 = [7, 9, 11, 13, 15] exp2 = [7 .. 11] ++ [8 .. 12] ++ [9 .. 13] ++ [10 .. 14] ++ [11 .. 15] res1 === exp1 res2 === exp2 prop_parBitraverse :: Property prop_parBitraverse = property $ do a <- forAll $ Gen.list (Range.linear 5 10) Gen.alpha b <- forAll $ Gen.int (Range.linear 5 10) c <- forAll Gen.bool let res1 = parBitraverse show show (a, b, c) res2 = bitraverse show show (a, b, c) length res1 === min (length $ show b) (length $ show c) length res2 === length (show b) * length (show c) -------------- Datatypes ------------------------- type Name = Refined NonEmpty String type Age = Refined (GreaterThan 17) Int data Person = Person { personAge :: Age , personName :: Name } deriving (Eq, Show) -------------- Sequential Validation ------------- mkPersonSeq :: Int -> String -> Either RefineException Person mkPersonSeq a n = do age <- refine a name <- refine n return $ Person age name -------------- Parallel Validation (manually) ------------------- type Eff a = Either [String] a ref :: Predicate p x => x -> Eff (Refined p x) ref x = left (\e -> [show e]) (refine x) mkPerson :: Int -> String -> Eff Person mkPerson a n = toEither $ Person <$> fromEither (ref a) <*> fromEither (ref n) -------------- Parallel Validation ------------- makePerson :: Int -> String -> Eff Person makePerson a n = parMap2 (ref a) (ref n) Person