packages feed

quiver-sort 0.1.0.0 → 0.2.0.0

raw patch · 4 files changed

+175/−37 lines, 4 filesdep +containersPVP ok

version bump matches the API change (PVP)

Dependencies added: containers

API changes (from Hackage documentation)

+ Control.Quiver.Sort: data SPFileConfig
+ Control.Quiver.Sort: defaultConfig :: SPFileConfig
+ Control.Quiver.Sort: setChunkSize :: Int -> SPFileConfig -> SPFileConfig
+ Control.Quiver.Sort: setMaxFiles :: Int -> SPFileConfig -> SPFileConfig
+ Control.Quiver.Sort: setTempDir :: FilePath -> SPFileConfig -> SPFileConfig
- Control.Quiver.Sort: spfilesort :: (Binary a, Ord a, MonadResource m, MonadMask m) => Maybe Int -> Maybe FilePath -> P () a a () m (SPResult IOException)
+ Control.Quiver.Sort: spfilesort :: (Binary a, Ord a, MonadResource m, MonadMask m) => SPFileConfig -> P () a a () m (SPResult IOException)
- Control.Quiver.Sort: spfilesortBy :: (Binary a, MonadResource m, MonadMask m) => (a -> a -> Ordering) -> Maybe Int -> Maybe FilePath -> P () a a () m (SPResult IOException)
+ Control.Quiver.Sort: spfilesortBy :: (Binary a, MonadResource m, MonadMask m) => (a -> a -> Ordering) -> SPFileConfig -> P () a a () m (SPResult IOException)

Files

quiver-sort.cabal view
@@ -1,5 +1,5 @@ name:                quiver-sort-version:             0.1.0.0+version:             0.2.0.0 synopsis:            Sort the values in a quiver description:   Allows sorting values within a Quiver, including using external@@ -11,6 +11,7 @@ category:            Control build-type:          Simple extra-source-files:  README.md+                   , stack.yaml cabal-version:       >=1.10  tested-with:   GHC == 7.10.2, GHC == 7.11.*@@ -23,6 +24,7 @@   exposed-modules:     Control.Quiver.Sort   -- other-modules:   build-depends:       base >=4.8 && <4.9+                     , containers                      , directory                      , exceptions                      , quiver >= 1.1.3 && < 1.2
src/Control/Quiver/Sort.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}+{-# LANGUAGE PatternSynonyms, RankNTypes, ScopedTypeVariables, ViewPatterns #-}+ {- |    Module      : Control.Quiver.Sort    Description : Sort values in a Quiver@@ -19,6 +20,12 @@     -- $filesort   , spfilesort   , spfilesortBy+    -- ** Configuration+  , SPFileConfig+  , defaultConfig+  , setChunkSize+  , setTempDir+  , setMaxFiles   ) where  import Control.Quiver.Binary@@ -28,21 +35,28 @@ import Control.Quiver.Interleave import Control.Quiver.SP -import Control.Applicative          (liftA2)-import Control.Exception            (IOException, finally)-import Control.Monad                (join)-import Control.Monad.Catch          (MonadCatch (..), MonadMask)-import Control.Monad.IO.Class       (MonadIO (..))-import Control.Monad.Trans.Resource (MonadResource, allocate)-import Data.Bool                    (bool)-import Data.Function                (on)-import Data.List                    (sortBy)-import Data.Maybe                   (fromMaybe)-import System.Directory             (doesDirectoryExist, getPermissions,-                                     getTemporaryDirectory,-                                     removeDirectoryRecursive, writable)-import System.IO                    (hClose, openTempFile)-import System.IO.Temp               (createTempDirectory)+import           Control.Applicative          (liftA2)+import           Control.Exception            (IOException)+import           Control.Monad                (join)+import           Control.Monad.Catch          (MonadCatch (..), MonadMask,+                                               finally)+import           Control.Monad.IO.Class       (MonadIO (..))+import           Control.Monad.Trans.Resource (MonadResource, allocate)+import           Data.Bool                    (bool)+import           Data.Coerce                  (coerce)+import           Data.Foldable                (toList)+import           Data.Function                (on)+import           Data.List                    (sortBy)+import           Data.Monoid                  (First (..), (<>))+import           Data.Sequence                (Seq, (|>))+import qualified Data.Sequence                as S+import           System.Directory             (doesDirectoryExist,+                                               getPermissions,+                                               getTemporaryDirectory,+                                               removeDirectoryRecursive,+                                               removeFile, writable)+import           System.IO                    (hClose, openTempFile)+import           System.IO.Temp               (createTempDirectory)  -------------------------------------------------------------------------------- @@ -59,7 +73,7 @@  -- | Use the specified comparison function to sort the values. spsortBy :: (Monad m) => (a -> a -> Ordering) -> SP a a m ()-spsortBy f = (sortBy f <$> spfoldr (:) []) >>= spevery+spsortBy f = (sortBy f <$> spToList) >>= spevery  -- | Use the provided function to be able to compare values. spsortOn :: (Ord b, Monad m) => (a -> b) -> SP a a m ()@@ -67,6 +81,9 @@              >->> spsortBy (compare `on` snd)              >->> sppure fst >&> snd +spToList :: SQ a x f [a]+spToList = spfoldr (:) []+ --------------------------------------------------------------------------------  {- $filesort@@ -77,13 +94,64 @@  -} +-- | Configuration settings for 'spfilesort' and 'spfilesortBy'.  Use+-- 'defaultConfig' and the various @set*@ functions to configure it.+data SPFileConfig = FC { _chunkSize  :: !Int+                         -- ^ How large the chunks should be for+                         -- individual sorting.+                       , _withTmpDir :: !(Maybe FilePath)+                         -- ^ Where to store temporary files.  Will be+                         -- cleaned up afterwards.  'Nothing'+                         -- indicates to use the system temporary+                         -- directory.+                       , _maxFiles   :: !Int+                         -- ^ The maximum number of temporary files to+                         -- be open at any one time.+                       }++-- | Default settings for sorting using external files:+--+--   * Have a chunk size of @1000@.+--+--   * Use the system temporary directory.+--+--   * No more than @100@ temporary files to be open at a time.+defaultConfig :: SPFileConfig+defaultConfig = FC { _chunkSize  = 1000+                   , _withTmpDir = Nothing+                   , _maxFiles   = 100+                   }++-- | Specify the size of chunks to be individually sorted: the larger+-- the value the fewer temporary files need to be created but the more+-- memory needed to accumulate the values and sort them.+setChunkSize :: Int -> SPFileConfig -> SPFileConfig+setChunkSize cs cfg = cfg { _chunkSize = cs }++-- | Specify where temporary files should be stored.+--+--   Typically you would only set this if the system temporary+--   directory isn't large or fast enough.+--+--   NOTE: this directory /must/ exist and be writable!+setTempDir :: FilePath -> SPFileConfig -> SPFileConfig+setTempDir dir cfg = cfg { _withTmpDir = Just dir }++-- | The maximum number of files that should be open at any one time.+--+--   Larger values will be faster, but run the risk of exhausting the+--   operating system's supply of file descriptors (and thus being+--   killed).+setMaxFiles :: Int -> SPFileConfig -> SPFileConfig+setMaxFiles c cfg = cfg { _maxFiles = c }+ -- | Use external files to temporarily store partially sorted results -- (splitting into chunks of the specified size if one is provided). -- -- These files are stored inside the specified directory if provided; -- if no such directory is provided then the system temporary -- directory is used.-spfilesort :: (Binary a, Ord a, MonadResource m, MonadMask m) => Maybe Int -> Maybe FilePath+spfilesort :: (Binary a, Ord a, MonadResource m, MonadMask m) => SPFileConfig               -> P () a a () m (SPResult IOException) spfilesort = spfilesortBy compare @@ -94,10 +162,10 @@ -- These files are stored inside the specified directory if provided; -- if no such directory is provided then the system temporary -- directory is used.-spfilesortBy :: (Binary a, MonadResource m, MonadMask m) => (a -> a -> Ordering) -> Maybe Int -> Maybe FilePath+spfilesortBy :: (Binary a, MonadResource m, MonadMask m) => (a -> a -> Ordering) -> SPFileConfig                 -> P () a a () m (SPResult IOException)-spfilesortBy cmp mchunks mdir = do mdir' <- join <$> liftIO (traverse checkDir mdir)-                                   getTmpDir mdir' "quiver-sort" pipeline+spfilesortBy cmp cfg = do mdir' <- join <$> liftIO (traverse checkDir (_withTmpDir cfg))+                          getTmpDir mdir' "quiver-sort" pipeline   where     -- Make sure the directory exists and is writable.     checkDir dir = do ex <- liftA2 (liftA2 (&&)) doesDirectoryExist (fmap writable . getPermissions) dir@@ -105,32 +173,53 @@      getTmpDir = maybe withSystemTempDirectory withTempDirectory -    pipeline tmpDir = toFiles tmpDir >>= either spfailed (sortFromFiles cmp)+    pipeline tmpDir = toFiles tmpDir >>= either spfailed (sortFromFiles maxFiles cmp tmpDir) -    toFiles tmpDir = sortToFiles chunkSize cmp tmpDir >->> spToList >&> uncurry (flip checkFailed)+    toFiles tmpDir = sortToFiles chunkSize cmp tmpDir >->> spToSeq >&> uncurry (flip checkFailed) -    chunkSize = fromMaybe 10000 mchunks+    chunkSize = _chunkSize cfg -sortToFiles :: (Binary a, MonadIO m) => Int -> (a -> a -> Ordering) -> FilePath+    maxFiles = _maxFiles cfg++sortToFiles :: (Binary a, MonadIO m, MonadMask m) => Int -> (a -> a -> Ordering) -> FilePath                -> SP a FilePath m IOException sortToFiles chunkSize cmp tmpDir = spchunks chunkSize                                    >->> spTraverseUntil sortChunk                                    >&> snd   where-    sortChunk as = liftIO $ do (fl,h) <- openTempFile tmpDir "quiver-sort-chunk"-                               finally (checkFailed fl <$> sprun (pipeline h)) (hClose h)-      where-        pipeline h = spevery (sortBy cmp as) >->> spencode >->> qPut h >&> snd+    sortChunk as = writeOut tmpDir (spevery (sortBy cmp as)) -sortFromFiles :: (Binary a, MonadIO m) => (a -> a -> Ordering) -> [FilePath]-                 -> Producer a () m (SPResult IOException)-sortFromFiles cmp fls = spinterleave cmp (map readFl fls)+writeOut :: (Binary a, MonadIO m, MonadMask m) => FilePath -> P () x a () m (SPResult IOException)+            -> m (Either IOException FilePath)+writeOut tmpDir p = do (fl,h) <- liftIO (openTempFile tmpDir "quiver-sort-chunk")+                       finally (checkFailed fl <$> sprun (pipeline h) <* liftIO (hClose h))+                               (liftIO (hClose h))   where+    pipeline h = p >->> spencode >&> fst >->> qhoist liftIO (qPut h) >&> getFirstError++sortFromFiles :: (Binary a, MonadIO m, MonadMask m) => Int -> (a -> a -> Ordering) -> FilePath+                 -> Seq FilePath -> Producer a () m (SPResult IOException)+sortFromFiles mf cmp tmpDir = nextBatch+  where+    nextBatch Empty = spcomplete+    nextBatch fls   = case S.splitAt mf fls of+                        (b,Empty) -> batch b+                        (b,fls')  -> do br <- qlift (writeBatch b)+                                        liftIO $ mapM_ removeFile b+                                        either spfailed (nextBatch . (fls' |>)) br++    writeBatch = writeOut tmpDir . batch++    batch = spinterleave cmp . map readFl . toList+     -- Assume decoding is successful for now.     readFl fl = qhoist liftIO (qReadFile fl readSize) >->> spdecode >&> fst      readSize = 4096 +-- Just to make it nicer to pattern-match+pattern Empty <- (S.viewl -> S.EmptyL)+ spTraverseUntil :: (Monad m) => (a -> m (Either e b)) -> SP a b m e spTraverseUntil k = loop   where@@ -142,8 +231,8 @@ checkFailed _ (Just (Just e)) = Left e checkFailed r _               = Right r -spToList :: SQ a x f [a]-spToList = spfoldr (:) []+spToSeq :: SQ a x f (Seq a)+spToSeq = spfoldl' (|>) mempty  -------------------------------------------------------------------------------- -- Creating the temporary directory@@ -166,3 +255,9 @@  ignoringIOErrors :: (MonadCatch m) => m () -> m () ignoringIOErrors ioe = ioe `catch` (\(_ :: IOError) -> return ())++getFirstError :: (SPResult a, SPResult a) -> SPResult a+getFirstError (r1,r2) = coerce (toFirst r1 <> toFirst r2)+  where+    toFirst :: SPResult a -> Maybe (First a)+    toFirst = coerce
+ stack.yaml view
@@ -0,0 +1,41 @@+# This file was automatically generated by stack init+# For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration/++# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)+resolver: lts-5.11++# Local packages, usually specified by relative directory name+packages:+- '.'+# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)+extra-deps:+- quiver-1.1.3+- quiver-binary-0.1.1.0+- quiver-bytestring-1.0.0+- quiver-groups-0.1.0.0+- quiver-instances-0.2.0.0+- quiver-interleave-0.2.0.0++# Override default flag values for local packages and extra-deps+flags: {}++# Extra package databases containing global packages+extra-package-dbs: []++# Control whether we use the GHC we find on the path+# system-ghc: true++# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+# require-stack-version: >= 1.0.0++# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64++# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]++# Allow a newer minor version of GHC than the snapshot specifies+# compiler-check: newer-minor
test/Spec.hs view
@@ -63,7 +63,7 @@ spList p as = spevery as >->> p >->> spToList >&> snd  fileSort :: (Binary a, Ord a) => Int -> [a] -> IO [a]-fileSort cs as = runResourceT $ sprun $ spList (spfilesort (Just cs) Nothing) as+fileSort cs as = runResourceT $ sprun $ spList (spfilesort (setChunkSize cs defaultConfig)) as  -- The provided producer is assumed to be short. fileSortCleanup :: (Binary a, Ord a) => Producer a () IO e -> Expectation@@ -76,6 +76,6 @@     pipeline tmpDir = qhoist lift prod                       -- Use a chunk size of 1 to make sure files are                       -- created, even if an exception is thrown.-                      >->> spfilesort (Just 1) (Just tmpDir)+                      >->> spfilesort (setChunkSize 1 . setTempDir tmpDir $ defaultConfig)                       >->> sptraverse_ (lift . void . evaluate) -- Just to consume them all                       >&> const ()