hackage-server-0.4: Distribution/Server/Util/CountingMap.hs
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances, DeriveDataTypeable, ScopedTypeVariables #-}
module Distribution.Server.Util.CountingMap (
NestedCountingMap(..)
, SimpleCountingMap(..)
, CountingMap(..)
, cmFromCSV
) where
import Prelude hiding (rem)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Typeable (Typeable)
import Text.CSV (CSV, Record)
import Control.Applicative ((<$>), (<*>))
import Data.SafeCopy (SafeCopy(..), safeGet, safePut, contain)
import Distribution.Text (Text(..), display)
import Distribution.Server.Framework.Instances ()
import Distribution.Server.Framework.MemSize
import Distribution.Server.Framework.BackupRestore (parseRead, parseText)
{------------------------------------------------------------------------------
We define some generic machinery to give us functions for arbitrarily
nested "counting maps".
We define SimpleCountingMap as a separate type from NestedCountingMap as
a hint to the type checker (we get into trouble with the functional
dependencies otherwise).
------------------------------------------------------------------------------}
data NestedCountingMap a b = NCM {
nestedTotalCount :: Int
, nestedCountingMap :: Map a b
}
deriving (Show, Eq, Typeable)
newtype SimpleCountingMap a = SCM {
simpleCountingMap :: NestedCountingMap a Int
}
deriving (Show, Eq, Typeable)
class CountingMap k a | a -> k where
cmEmpty :: a
cmTotal :: a -> Int
cmInsert :: k -> Int -> a -> a
cmFind :: k -> a -> Int
cmToList :: a -> [(k, Int)]
cmToCSV :: a -> CSV
cmInsertRecord :: Monad m => Record -> a -> m (a, Int)
instance (Ord k, Text k) => CountingMap k (SimpleCountingMap k) where
cmEmpty = SCM (NCM 0 Map.empty)
cmTotal (SCM (NCM total _)) = total
cmInsert k n (SCM (NCM total m)) =
SCM (NCM (total + n) (adjustFrom (+ n) k 0 m))
cmFind k (SCM (NCM _ m)) = Map.findWithDefault 0 k m
cmToList (SCM (NCM _ m)) = Map.toList m
cmToCSV (SCM (NCM _ m)) = map aux (Map.toList m)
where
aux :: (k, Int) -> Record
aux (k, n) = [display k, show n]
cmInsertRecord [k, n] m = do
key <- parseText "key" k
count <- parseRead "count" n
return (cmInsert key count m, count)
cmInsertRecord _ _ =
fail "cmInsertRecord: Invalid record"
instance (Text k, Ord k, Eq l, CountingMap l a) => CountingMap (k, l) (NestedCountingMap k a) where
cmEmpty = NCM 0 Map.empty
cmTotal (NCM total _m) = total
cmInsert (k, l) n (NCM total m) =
NCM (total + n) (adjustFrom (cmInsert l n) k cmEmpty m)
cmFind (k, l) (NCM _ m) = cmFind l (Map.findWithDefault cmEmpty k m)
cmToList (NCM _ m) = concatMap aux (Map.toList m)
where
aux :: (k, a) -> [((k, l), Int)]
aux (k, m') = map (\(l, c) -> ((k, l), c)) (cmToList m')
cmToCSV (NCM _ m) = concatMap aux (Map.toList m)
where
aux :: (k, a) -> CSV
aux (k, m') = map (display k:) (cmToCSV m')
cmInsertRecord (k : record) (NCM total m) = do
key <- parseText "key" k
let submap = Map.findWithDefault cmEmpty key m
(submap', added) <- cmInsertRecord record submap
return (NCM (total + added) (Map.insert key submap' m), added)
cmInsertRecord [] _ =
fail "cmInsertRecord: Invalid record"
cmFromCSV :: (Monad m, CountingMap k a) => CSV -> m a
cmFromCSV = go cmEmpty
where
go acc [] = return acc
go acc (r:rs) = do
(acc', _) <- cmInsertRecord r acc
go acc' rs
{------------------------------------------------------------------------------
Auxiliary
------------------------------------------------------------------------------}
adjustFrom :: Ord k => (a -> a) -> k -> a -> Map k a -> Map k a
adjustFrom func key value = Map.alter (Just . func . fromMaybe value) key
{------------------------------------------------------------------------------
Type classes instances
------------------------------------------------------------------------------}
instance MemSize a => MemSize (SimpleCountingMap a) where
memSize (SCM m) = memSize m
instance (MemSize a, MemSize b) => MemSize (NestedCountingMap a b) where
memSize (NCM a b) = memSize2 a b
instance (Ord a, SafeCopy a, SafeCopy b) => SafeCopy (NestedCountingMap a b) where
putCopy (NCM total m) = contain $ do safePut total ; safePut m
getCopy = contain $ NCM <$> safeGet <*> safeGet
instance (Ord a, SafeCopy a) => SafeCopy (SimpleCountingMap a) where
putCopy (SCM m) = contain $ safePut m
getCopy = contain $ SCM <$> safeGet