1
+ {-# LANGUAGE CPP #-}
1
2
{-# LANGUAGE FlexibleContexts #-}
2
3
{-# LANGUAGE RankNTypes #-}
3
4
{-# LANGUAGE ScopedTypeVariables #-}
@@ -11,20 +12,25 @@ module Test.IOSim
11
12
import Data.Array
12
13
import Data.Either (isLeft )
13
14
import Data.Fixed (Fixed (.. ), Micro )
15
+ import Data.Foldable (foldl' )
14
16
import Data.Function (on )
17
+ import Data.Functor (($>) )
15
18
import Data.Graph
16
19
import Data.List (sortBy )
17
20
import Data.Time.Clock (picosecondsToDiffTime )
18
21
19
22
import Control.Exception (ArithException (.. ))
20
23
import Control.Monad
24
+ import Control.Monad.Fix
21
25
import System.IO.Error (ioeGetErrorString , isUserError )
22
26
23
27
import Control.Monad.Class.MonadFork
24
28
import Control.Monad.Class.MonadSTM.Strict
25
29
import Control.Monad.Class.MonadSay
30
+ import qualified Control.Monad.Class.MonadSTM as LazySTM
26
31
import Control.Monad.Class.MonadThrow
27
32
import Control.Monad.Class.MonadTimer
33
+ import Control.Monad.Class.MonadTime
28
34
import Control.Monad.IOSim
29
35
30
36
import Test.STM
@@ -131,6 +137,13 @@ tests =
131
137
[ testProperty " Reference vs IO" prop_stm_referenceIO
132
138
, testProperty " Reference vs Sim" prop_stm_referenceSim
133
139
]
140
+ , testGroup " MonadFix instance"
141
+ [ testProperty " purity" prop_mfix_purity
142
+ , testProperty " purity2" prop_mfix_purity_2
143
+ , testProperty " tightening" prop_mfix_left_shrinking
144
+ , testProperty " lazy" prop_mfix_lazy
145
+ , testProperty " recdata" prop_mfix_recdata
146
+ ]
134
147
]
135
148
136
149
@@ -401,6 +414,155 @@ test_wakeup_order = do
401
414
return (wakupOrder === [0 .. 9 ]) -- FIFO order
402
415
403
416
417
+ --
418
+ -- MonadFix properties
419
+ --
420
+
421
+ -- | Purity demands that @mfix (return . f) = return (fix f)@.
422
+ --
423
+ prop_mfix_purity :: Positive Int -> Bool
424
+ prop_mfix_purity (Positive n) =
425
+ runSimOrThrow
426
+ (mfix (return . factorial)) n
427
+ == fix factorial n
428
+ where
429
+ factorial :: (Int -> Int ) -> Int -> Int
430
+ factorial = \ rec_ k -> if k <= 1 then 1 else k * rec_ (k - 1 )
431
+
432
+
433
+ prop_mfix_purity_2 :: [Positive Int ] -> Bool
434
+ prop_mfix_purity_2 as =
435
+ -- note: both 'IOSim' expressions are equivalent using 'Monad' and
436
+ -- 'Applicative' laws only.
437
+ runSimOrThrow (join $ mfix (return . recDelay)
438
+ <*> return as')
439
+ == expected
440
+ &&
441
+ runSimOrThrow (mfix (return . recDelay) >>= ($ as'))
442
+ == expected
443
+ where
444
+ as' :: [Int ]
445
+ as' = getPositive `map` as
446
+
447
+ -- recursive sum using 'threadDelay'
448
+ recDelay :: ( MonadMonotonicTime m
449
+ , MonadDelay m
450
+ )
451
+ => ([Int ] -> m Time )
452
+ -> [Int ] -> m Time
453
+ recDelay = \ rec_ bs ->
454
+ case bs of
455
+ [] -> getMonotonicTime
456
+ (b : bs') -> threadDelay (realToFrac b)
457
+ >> rec_ bs'
458
+
459
+ expected :: Time
460
+ expected = foldl' (flip addTime)
461
+ (Time 0 )
462
+ (realToFrac `map` as')
463
+
464
+
465
+ prop_mfix_left_shrinking
466
+ :: Int
467
+ -> NonNegative Int
468
+ -> Positive Int
469
+ -> Bool
470
+ prop_mfix_left_shrinking n (NonNegative d) (Positive i) =
471
+ let mn :: IOSim s Int
472
+ mn = do say " "
473
+ threadDelay (realToFrac d)
474
+ return n
475
+ in
476
+ take i
477
+ (runSimOrThrow $
478
+ mfix (\ rec_ -> mn >>= \ a -> do
479
+ threadDelay (realToFrac d) $> a : rec_))
480
+ ==
481
+ take i
482
+ (runSimOrThrow $
483
+ mn >>= \ a ->
484
+ (mfix (\ rec_ -> do
485
+ threadDelay (realToFrac d) $> a : rec_)))
486
+
487
+
488
+
489
+ -- | 'Example 8.2.1' in 'Value Recursion in Monadic Computations'
490
+ -- <https://leventerkok.github.io/papers/erkok-thesis.pdf>
491
+ --
492
+ prop_mfix_lazy :: NonEmptyList Char
493
+ -> Bool
494
+ prop_mfix_lazy (NonEmpty env) =
495
+ take samples
496
+ (runSimOrThrow (withEnv (mfix . replicateHeadM)))
497
+ == replicate samples (head env)
498
+ where
499
+ samples :: Int
500
+ samples = 10
501
+
502
+ replicateHeadM ::
503
+ (
504
+ #if MIN_VERSION_base(4,13,0)
505
+ MonadFail m,
506
+ MonadFail (STM m),
507
+ #endif
508
+ MonadSTM m
509
+ )
510
+ => m Char
511
+ -> [Char ] -> m [Char ]
512
+ replicateHeadM getChar_ as = do
513
+ -- Note: 'getChar' will be executed only once! This follows from 'fixIO`
514
+ -- semantics.
515
+ a <- getChar_
516
+ return (a : as)
517
+
518
+ -- construct 'getChar' using the simulated environment
519
+ withEnv :: (
520
+ #if MIN_VERSION_base(4,13,0)
521
+ MonadFail m,
522
+ #endif
523
+ MonadSTM m
524
+ )
525
+ => (m Char -> m a) -> m a
526
+ withEnv k = do
527
+ v <- newTVarIO env
528
+ let getChar_ =
529
+ atomically $ do
530
+ as <- readTVar v
531
+ case as of
532
+ [] -> error " withEnv: runtime error"
533
+ (a : as') -> writeTVar v as'
534
+ $> a
535
+ k getChar_
536
+
537
+
538
+ -- | 'Example 8.2.3' in 'Value Recursion in Monadic Computations'
539
+ -- <https://leventerkok.github.io/papers/erkok-thesis.pdf>
540
+ --
541
+ prop_mfix_recdata :: Property
542
+ prop_mfix_recdata = ioProperty $ do
543
+ expected <- experiment
544
+ let res = runSimOrThrow experiment
545
+ return $
546
+ take samples res
547
+ ==
548
+ take samples expected
549
+ where
550
+ samples :: Int
551
+ samples = 10
552
+
553
+ experiment :: ( MonadSTM m
554
+ , MonadFix m
555
+ )
556
+ => m [Int ]
557
+ experiment = do
558
+ (_, y) <-
559
+ mfix (\ ~ (x, _) -> do
560
+ y <- LazySTM. newTVarIO x
561
+ return (1 : x, y)
562
+ )
563
+ atomically (LazySTM. readTVar y)
564
+
565
+
404
566
--
405
567
-- Probe mini-abstraction
406
568
--
0 commit comments