packages feed

scheduler 2.0.0.1 → 2.0.1.0

raw patch · 8 files changed

+121/−87 lines, 8 filesdep +quickcheck-classesdep −doctestdep −genvalidity-hspecdep −mwc-randomdep ~QuickCheckdep ~basedep ~primitive

Dependencies added: quickcheck-classes

Dependencies removed: doctest, genvalidity-hspec, mwc-random, vector

Dependency ranges changed: QuickCheck, base, primitive, unliftio-core

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@-# 2.0.0+# `scheduler` +## 2.0.1++* Add `Eq1` and `Show1` instances for `Result`+* Add compatibility with older `primitive`++## 2.0.0+ * Switch type parameter of `Scheduler` from monad `m` to state token `s`. Which   also means that constraints on many functions got tighter (i.e. `MonadPrim`, `MonadPrimBase`) * Remove `m` type parameter for `SchedulerWS`, since it can only work in `IO` like monad anyways.@@ -7,7 +14,7 @@ * Swap order of arguments for `replicateWork` for consistency * Add `replicateWork_` that is slightly more efficient than `replicateWork` -# 1.5.0+## 1.5.0  Despite that the major part of the version was bumped up, this release does not include any breaking changes, only improvements and additions.@@ -24,7 +31,7 @@ * Addition of `GlobalScheduler` that can be reused throughout the codebase, thus reducing   initialization overhead. -# 1.4.2+## 1.4.2  * Add `withTrivialScheduler` * Add `Results` data type as well as corresponding functions:@@ -32,11 +39,11 @@   * `withSchedulerWSR`   * `withTrivialSchedulerR` -# 1.4.1+## 1.4.1  * Add functions: `replicateWork` -# 1.4.0+## 1.4.0  * Worker id has been promoted from `Int` to a `newtype` wrapper `WorkerId`. * Addition of `SchedulerWS` and `WorkerStates` data types. As well as the@@ -51,18 +58,18 @@   * `unwrapSchedulerWS` * Made internal modules accessible, but invisible. -# 1.3.0+## 1.3.0  * Make sure internal `Scheduler` accessor functions are no longer exported, they only   cause breakage. * Make sure number of capabilities does not change through out the program execution, as   far as `scheduler` is concerned. -# 1.2.0+## 1.2.0  * Addition of `scheduleWorkId` and `scheduleWorkId_` -# 1.1.0+## 1.1.0  * Add functions: `replicateConcurrently` and `replicateConcurrently_` * Made `traverseConcurrently_` lazy, thus making it possible to apply to infinite lists and other such@@ -70,6 +77,6 @@ * Fix `Monoid` instance for `Comp` * Addition of `Par'` pattern -# 1.0.0+## 1.0.0  Initial release.
scheduler.cabal view
@@ -1,8 +1,8 @@ name:                scheduler-version:             2.0.0.1+version:             2.0.1.0 synopsis:            Work stealing scheduler. description:         A work stealing scheduler that is designed for parallelization of heavy work loads. It was primarily developed for [massiv](https://github.com/lehins/massiv) array library, but it is general enough to be useful for any computation that fits the model of few workers and many jobs.-homepage:            https://github.com/lehins/haskell-scheduler+homepage:            https://github.com/lehins/scheduler license:             BSD3 license-file:        LICENSE author:              Alexey Kuleshevich@@ -13,6 +13,19 @@ extra-source-files:  README.md                    , CHANGELOG.md cabal-version:       >=1.10+tested-with:          GHC == 8.0.2+                    , GHC == 8.2.2+                    , GHC == 8.4.4+                    , GHC == 8.6.5+                    , GHC == 8.8.6+                    , GHC == 8.10.7+                    , GHC == 9.0.2+                    , GHC == 9.2.8+                    , GHC == 9.4.8+                    , GHC == 9.6.6+                    , GHC == 9.8.4+                    , GHC == 9.10.1+                    , GHC == 9.12.1  library   hs-source-dirs:      src@@ -26,8 +39,8 @@                      , atomic-primops                      , deepseq                      , exceptions-                     , unliftio-core-                     , primitive      >= 0.7.1+                     , unliftio-core  >= 0.1.1+                     , primitive      >= 0.6.4                      , pvar           < 2.0   default-language:    Haskell2010   ghc-options:         -Wall -Wno-simplifiable-class-constraints@@ -40,12 +53,12 @@   other-modules:      Spec                     , Control.SchedulerSpec   build-tool-depends: hspec-discover:hspec-discover-  build-depends:      base+  build-depends:      base           >= 4.9 && < 5                     , deepseq-                    , genvalidity-hspec                     , scheduler                     , hspec                     , QuickCheck+                    , quickcheck-classes >= 0.6                     , unliftio    default-language:   Haskell2010@@ -55,12 +68,7 @@   type:             exitcode-stdio-1.0   hs-source-dirs:   tests   main-is:          doctests.hs-  build-depends: base-  if impl(ghc >= 8.2) && impl(ghc < 8.10)-    build-depends: doctest           >=0.15-                 , mwc-random-                 , scheduler-                 , vector+  build-depends:    base           >= 4.9 && < 5   default-language:    Haskell2010  source-repository head
src/Control/Scheduler.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MonoLocalBinds #-}-{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} -- | -- Module      : Control.Scheduler -- Copyright   : (c) Alexey Kuleshevich 2018-2021@@ -162,7 +162,7 @@ -- | Schedule a job that will get a worker state passed as an argument -- -- @since 1.4.0-scheduleWorkState :: MonadPrimBase RealWorld m => SchedulerWS ws a -> (ws -> m a) -> m ()+scheduleWorkState :: (PrimBase m, RealWorld ~ PrimState m) => SchedulerWS ws a -> (ws -> m a) -> m () scheduleWorkState schedulerS withState =   scheduleWorkId (_getScheduler schedulerS) $ \(WorkerId i) ->     withState (indexSmallArray (_workerStatesArray (_workerStates schedulerS)) i)@@ -170,7 +170,7 @@ -- | Same as `scheduleWorkState`, but dont' keep the result of computation. -- -- @since 1.4.0-scheduleWorkState_ :: MonadPrimBase RealWorld m => SchedulerWS ws () -> (ws -> m ()) -> m ()+scheduleWorkState_ :: (PrimBase m, RealWorld ~ PrimState m) => SchedulerWS ws () -> (ws -> m ()) -> m () scheduleWorkState_ schedulerS withState =   scheduleWorkId_ (_getScheduler schedulerS) $ \(WorkerId i) ->     withState (indexSmallArray (_workerStatesArray (_workerStates schedulerS)) i)@@ -191,7 +191,7 @@ -- scheduler. -- -- @since 1.2.0-scheduleWorkId :: MonadPrimBase s m => Scheduler s a -> (WorkerId -> m a) -> m ()+scheduleWorkId :: (PrimBase m, s ~ PrimState m) => Scheduler s a -> (WorkerId -> m a) -> m () scheduleWorkId s f = stToPrim (_scheduleWorkId s (primToPrim . f))  -- | As soon as possible try to terminate any computation that is being performed by all@@ -203,7 +203,7 @@ -- although it will make sure their results are discarded. -- -- @since 1.1.0-terminate :: MonadPrim s m => Scheduler s a -> a -> m a+terminate :: (PrimMonad m, s ~ PrimState m) => Scheduler s a -> a -> m a terminate scheduler a = stToPrim (_terminate scheduler (Early a))  -- | Same as `terminate`, but returning a single element list containing the supplied@@ -216,14 +216,14 @@ -- function. -- -- @since 1.1.0-terminateWith :: MonadPrim s m => Scheduler s a -> a -> m a+terminateWith :: (PrimMonad m, s ~ PrimState m) => Scheduler s a -> a -> m a terminateWith scheduler a = stToPrim $ _terminate scheduler (EarlyWith a)  -- | Schedule an action to be picked up and computed by a worker from a pool of -- jobs. Similar to `scheduleWorkId`, except the job doesn't get the worker id. -- -- @since 1.0.0-scheduleWork :: MonadPrimBase s m => Scheduler s a -> m a -> m ()+scheduleWork :: (PrimBase m, s ~ PrimState m) => Scheduler s a -> m a -> m () scheduleWork scheduler f = stToPrim $ _scheduleWorkId scheduler (const (primToPrim f))  @@ -232,13 +232,13 @@ -- | Same as `scheduleWork`, but only for a `Scheduler` that doesn't keep the results. -- -- @since 1.1.0-scheduleWork_ :: MonadPrimBase s m => Scheduler s () -> m () -> m ()+scheduleWork_ :: (PrimBase m, s ~ PrimState m) => Scheduler s () -> m () -> m () scheduleWork_ s = stToPrim . scheduleWork s . primToPrim  -- | Same as `scheduleWorkId`, but only for a `Scheduler` that doesn't keep the results. -- -- @since 1.2.0-scheduleWorkId_ :: MonadPrimBase s m => Scheduler s () -> (WorkerId -> m ()) -> m ()+scheduleWorkId_ :: (PrimBase m, s ~ PrimState m) => Scheduler s () -> (WorkerId -> m ()) -> m () scheduleWorkId_ scheduler f = stToPrim $ _scheduleWorkId scheduler (primToPrim . f)  -- | Schedule the same action to run @n@ times concurrently. This differs from@@ -247,7 +247,7 @@ -- To be called within a `withScheduler` block. -- -- @since 2.0.0-replicateWork :: MonadPrimBase s m => Scheduler s a -> Int -> m a -> m ()+replicateWork :: (PrimBase m, s ~ PrimState m) => Scheduler s a -> Int -> m a -> m () replicateWork scheduler n f = go n   where     go !k@@ -258,7 +258,7 @@ -- | Same as `replicateWork`, but it does not retain the results of scheduled jobs -- -- @since 2.0.0-replicateWork_ :: MonadPrimBase s m => Scheduler s () -> Int -> m a -> m ()+replicateWork_ :: (PrimBase m, s ~ PrimState m) => Scheduler s () -> Int -> m a -> m () replicateWork_ scheduler n f = go n   where     go !k@@ -270,7 +270,7 @@ -- /Important/ - In case of `Seq` computation strategy this function has no affect. -- -- @since 1.1.0-terminate_ :: MonadPrim s m => Scheduler s () -> m ()+terminate_ :: (PrimMonad m, s ~ PrimState m) => Scheduler s () -> m () terminate_ s = stToPrim $ _terminate s (Early ())  @@ -278,7 +278,7 @@ -- computation strategy, except it is restricted to `PrimMonad`, instead of `MonadUnliftIO`. -- -- @since 1.4.2-withTrivialScheduler :: MonadPrim s m => (Scheduler s a -> m b) -> m [a]+withTrivialScheduler :: (PrimMonad m, s ~ PrimState m) => (Scheduler s a -> m b) -> m [a] withTrivialScheduler action = F.toList <$> withTrivialSchedulerR action  @@ -305,7 +305,8 @@ traverseConcurrently_ :: (MonadUnliftIO m, Foldable t) => Comp -> (a -> m b) -> t a -> m () traverseConcurrently_ comp f xs =   withRunInIO $ \run ->-    withScheduler_ comp $ \s -> scheduleWork s $ F.traverse_ (scheduleWork s . void . run . f) xs+    withScheduler_ comp $ \s -> F.traverse_ (scheduleWork_ s . void . run . f) xs+{-# INLINE traverseConcurrently_ #-}  -- | Replicate an action @n@ times and schedule them acccording to the supplied computation -- strategy.@@ -315,6 +316,7 @@ replicateConcurrently comp n f =   withRunInIO $ \run ->     withScheduler comp $ \s -> replicateM_ n $ scheduleWork s (run f)+{-# INLINE replicateConcurrently #-}  -- | Just like `replicateConcurrently`, but discards the results of computation. --@@ -322,8 +324,8 @@ replicateConcurrently_ :: MonadUnliftIO m => Comp -> Int -> m a -> m () replicateConcurrently_ comp n f =   withRunInIO $ \run -> do-    withScheduler_ comp $ \s -> scheduleWork s $ replicateM_ n (scheduleWork s $ void $ run f)-+    withScheduler_ comp $ \s -> replicateM_ n (scheduleWork_ s $ void $ run f)+{-# INLINE replicateConcurrently_ #-}   @@ -410,7 +412,7 @@ -- | Check if the supplied batch has already finished. -- -- @since 1.5.0-hasBatchFinished :: MonadPrim s m => Batch s a -> m Bool+hasBatchFinished :: (PrimMonad m, s ~ PrimState m) => Batch s a -> m Bool hasBatchFinished = stToPrim . batchHasFinished {-# INLINE hasBatchFinished #-} @@ -423,21 +425,21 @@ -- concurrent cancelation and it will return `True`. -- -- @since 1.5.0-cancelBatch :: MonadPrim s m => Batch s a -> a -> m Bool+cancelBatch :: (PrimMonad m, s ~ PrimState m) => Batch s a -> a -> m Bool cancelBatch b = stToPrim . batchCancel b {-# INLINE cancelBatch #-}  -- | Same as `cancelBatch`, but only works with schedulers that don't care about results -- -- @since 1.5.0-cancelBatch_ :: MonadPrim s m => Batch s () -> m Bool+cancelBatch_ :: (PrimMonad m, s ~ PrimState m) => Batch s () -> m Bool cancelBatch_ b = stToPrim $ batchCancel b () {-# INLINE cancelBatch_ #-}  -- | Same as `cancelBatch_`, but the result of computation will be set to `FinishedEarlyWith` -- -- @since 1.5.0-cancelBatchWith :: MonadPrim s m => Batch s a -> a -> m Bool+cancelBatchWith :: (PrimMonad m, s ~ PrimState m) => Batch s a -> a -> m Bool cancelBatchWith b = stToPrim . batchCancelWith b {-# INLINE cancelBatchWith #-} @@ -446,7 +448,7 @@ -- -- @since 1.5.0 getCurrentBatch ::-     MonadPrim s m => Scheduler s a -> m (Batch s a)+     (PrimMonad m, s ~ PrimState m) => Scheduler s a -> m (Batch s a) getCurrentBatch scheduler = stToPrim $ do   batchId <- _currentBatchId scheduler   pure $ Batch@@ -471,7 +473,7 @@ -- scheduler prior to starting the batch it will end up on the batch result list. -- -- @since 1.5.0-runBatch :: MonadPrimBase s m => Scheduler s a -> (Batch s a -> m c) -> m [a]+runBatch :: (PrimBase m, s ~ PrimState m) => Scheduler s a -> (Batch s a -> m c) -> m [a] runBatch scheduler f = stToPrim $ do   _ <- primToPrim . f =<< getCurrentBatch scheduler   reverse . resultsToList <$> _waitForCurrentBatch scheduler@@ -481,7 +483,7 @@ -- -- @since 1.5.0 runBatch_ ::-     MonadPrimBase s m => Scheduler s () -> (Batch s () -> m c) -> m ()+     (PrimBase m, s ~ PrimState m) => Scheduler s () -> (Batch s () -> m c) -> m () runBatch_ scheduler f = stToPrim $ do   _ <- primToPrim . f =<< getCurrentBatch scheduler   void (_waitForCurrentBatch scheduler)@@ -492,7 +494,7 @@ -- -- @since 1.5.0 runBatchR ::-     MonadPrimBase s m => Scheduler s a -> (Batch s a -> m c) -> m (Results a)+     (PrimBase m, s ~ PrimState m) => Scheduler s a -> (Batch s a -> m c) -> m (Results a) runBatchR scheduler f = stToPrim $ do   _ <- primToPrim . f =<< getCurrentBatch scheduler   reverseResults <$> _waitForCurrentBatch scheduler@@ -520,7 +522,5 @@ False >>> didAWorkerDie $ withScheduler Par $ \ s -> scheduleWork s $ myThreadId >>= killThread >> pure False True->>> withScheduler Par $ \ s -> scheduleWork s $ myThreadId >>= killThread >> pure False-*** Exception: thread killed  -}
src/Control/Scheduler/Computation.hs view
@@ -72,7 +72,7 @@ instance Monoid Comp where   mempty = Seq   {-# INLINE mempty #-}-  mappend = joinComp+  mappend = (<>)   {-# INLINE mappend #-}  instance Semigroup Comp where
src/Control/Scheduler/Internal.hs view
@@ -1,10 +1,11 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} {-# OPTIONS_HADDOCK hide, not-home #-} -- | -- Module      : Control.Scheduler.Internal@@ -115,23 +116,23 @@ -- rather computed immediately. -- -- @since 1.4.2-withTrivialSchedulerR :: forall a b m s. MonadPrim s m => (Scheduler s a -> m b) -> m (Results a)+withTrivialSchedulerR :: forall a b m s. (PrimMonad m, s ~ PrimState m) => (Scheduler s a -> m b) -> m (Results a) withTrivialSchedulerR action = do   resVar <- newMutVar []   batchVar <- newMutVar $ BatchId 0   finResVar <- newMutVar Nothing   batchEarlyVar <- newMutVar Nothing-  let bumpCurrentBatchId :: MonadPrim s m' => m' ()+  let bumpCurrentBatchId :: (PrimMonad m', s ~ PrimState m') => m' ()       bumpCurrentBatchId = atomicModifyMutVar' batchVar (\(BatchId x) -> (BatchId (x + 1), ()))-      bumpBatchId :: MonadPrim s m' => BatchId -> m' Bool+      bumpBatchId :: (PrimMonad m', s ~ PrimState m') => BatchId -> m' Bool       bumpBatchId (BatchId c) =         atomicModifyMutVar' batchVar $ \b@(BatchId x) ->           if x == c             then (BatchId (x + 1), True)             else (b, False)-      takeBatchEarly :: MonadPrim s m' => m' (Maybe (Early a))+      takeBatchEarly :: (PrimMonad m', s ~ PrimState m') => m' (Maybe (Early a))       takeBatchEarly = atomicModifyMutVar' batchEarlyVar $ \mEarly -> (Nothing, mEarly)-      takeResults :: MonadPrim s m' => m' [a]+      takeResults :: (PrimMonad m', s ~ PrimState m') => m' [a]       takeResults = atomicModifyMutVar' resVar $ \res -> ([], res)   _ <-     action $
src/Control/Scheduler/Types.hs view
@@ -34,6 +34,7 @@ import Control.Exception import Control.Scheduler.Computation import Control.Scheduler.Queue+import Data.Functor.Classes import Data.IORef import Data.Primitive.SmallArray import Data.Primitive.PVar@@ -77,6 +78,22 @@       Finished xs -> Finished <$> traverse f xs       FinishedEarly xs x -> FinishedEarly <$> traverse f xs <*> f x       FinishedEarlyWith x -> FinishedEarlyWith <$> f x++instance Eq1 Results where+  liftEq f (Finished xs1) (Finished xs2) = liftEq f xs1 xs2+  liftEq f (FinishedEarly xs1 x1) (FinishedEarly xs2 x2) = liftEq f xs1 xs2 && f x1 x2+  liftEq f (FinishedEarlyWith x1) (FinishedEarlyWith x2) = f x1 x2+  liftEq _ _ _ = False++instance Show1 Results where+  liftShowsPrec f g n = \case+    Finished xs -> wrap (("Finished " ++) . g xs)+    FinishedEarly xs x -> wrap (("FinishedEarly " ++) . g xs . (" " ++) . f 11 x)+    FinishedEarlyWith x -> wrap (("FinishedEarlyWith " ++) . f 11 x)+    where+      wrap s+        | n <= 1 = s+        | otherwise = ('(' :) . s . (++ ")")  data Jobs m a = Jobs   { jobsNumWorkers       :: {-# UNPACK #-} !Int
tests/Control/SchedulerSpec.hs view
@@ -20,16 +20,13 @@ import qualified Data.Foldable as F (toList, traverse_) import Data.IORef import Data.List (groupBy, sort, sortOn)+import Data.Proxy import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck+import Test.QuickCheck.Classes import Test.QuickCheck.Function import Test.QuickCheck.Monadic-import Test.Validity.Eq-import Test.Validity.Functor-import Test.Validity.Monoid-import Test.Validity.Ord-import Test.Validity.Show import UnliftIO.Async import UnliftIO.Exception hiding (assert) #if !MIN_VERSION_base(4,11,0)@@ -514,7 +511,6 @@ --     res' <- withSchedulerR comp scheduleJobs --     pure (res === [n] .&&. res' === FinishedEarlyWith n) - spec :: Spec spec = do   describe "Comp" $ do@@ -525,27 +521,40 @@         property $ \(x :: Comp) y z -> x <> (y <> z) === (x <> y) <> z       it "mconcat = foldr '(<>)' mempty" $         property $ \(xs :: [Comp]) -> mconcat xs === foldr (<>) mempty xs-      eqSpecOnArbitrary @Comp-      monoidSpecOnArbitrary @Comp+    it "Laws" $+      lawsCheckOne (Proxy @Comp)+        [ eqLaws+        , showLaws+        , semigroupLaws+        , monoidLaws+        ]     describe "Show" $ do       it "show == showsPrec 0" $ property $ \(x :: Comp) -> x `deepseq` show x === showsPrec 0 x ""       it "(show) == showsPrec 1" $         property $ \(x :: Comp) (Positive n) ->           x /= Seq && x /= Par ==> ("(" <> show x <> ")" === showsPrec n x "")   describe "Results" $ do-    eqSpecOnArbitrary @(Results Int)-    functorSpecOnArbitrary @Results-    showReadSpecOnArbitrary @(Results Int)+    it "Laws" $ do+      lawsCheckOne (Proxy @(Results Int))+        [ eqLaws+        , showLaws+        , showReadLaws+        ]+      lawsCheck (functorLaws (Proxy @Results))     it "Traversable" $ property $ \(rs :: Results Int) (f :: Fun Int (Maybe Int)) ->       traverse (apply f) (F.toList rs) === fmap F.toList (traverse (apply f) rs)   describe "WorkerId" $ do-    eqSpecOnArbitrary @WorkerId-    ordSpecOnArbitrary @WorkerId     it "MaxMin" $ property $ \x y ->       conjoin [ max (WorkerId x) (WorkerId y) === WorkerId (max x y)               , min (WorkerId x) (WorkerId y) === WorkerId (min x y)               ]-    showReadSpecOnArbitrary @WorkerId+    it "Laws" $ do+      lawsCheckOne (Proxy @WorkerId)+        [ eqLaws+        , ordLaws+        , showLaws+        , showReadLaws+        ]     describe "Enum" $ do       it "toEnumFromEnum" $ property $ \ wid@(WorkerId i) ->         toEnum (getWorkerId wid) === wid .&&. fromEnum wid === i@@ -621,13 +630,17 @@   arbitrary = WorkerId <$> arbitrary  instance Arbitrary a => Arbitrary (Results a) where-  arbitrary =+  arbitrary = liftArbitrary arbitrary++instance Arbitrary1 Results where+  liftArbitrary gen =     oneof-      [ Finished <$> arbitrary-      , FinishedEarly <$> arbitrary <*> arbitrary-      , FinishedEarlyWith <$> arbitrary+      [ Finished <$> listOf gen+      , FinishedEarly <$> listOf gen <*> gen+      , FinishedEarlyWith <$> gen       ] +#if !MIN_VERSION_QuickCheck(2,15,0) -- | Assert a synchronous exception assertExceptionIO :: (NFData a, Exception exc) =>                      (exc -> Bool) -- ^ Return True if that is the exception that was expected@@ -641,6 +654,7 @@         (do res <- action             res `deepseq` return False) $ \exc -> displayException exc `deepseq` return (isExc exc)     assert hasFailed+#endif  assertAsyncExceptionIO :: (Exception e, NFData a) => (e -> Bool) -> IO a -> Property assertAsyncExceptionIO isAsyncExc action =
tests/doctests.hs view
@@ -1,17 +1,4 @@-{-# LANGUAGE CPP #-} module Main where -#if __GLASGOW_HASKELL__ >= 802 && __GLASGOW_HASKELL__ < 810--import Test.DocTest (doctest)- main :: IO ()-main = doctest ["src"]--#else---- TODO: fix doctest support-main :: IO ()-main = putStrLn "\nDoctests are not supported for ghc version 8.2 and prior as well as 8.10\n"--#endif+main = pure ()