{-# LANGUAGE BangPatterns #-} {-# LANGUAGE Rank2Types #-} -- | -- Module : Data.Histogram.Fill -- Copyright : Copyright (c) 2009, Alexey Khudyakov <alexey.skladnoy@gmail.com> -- License : BSD3 -- Maintainer : Alexey Khudyakov <alexey.skladnoy@gmail.com> -- Stability : experimental -- -- Stateful and pure (still stateful under the hood) accumulators. -- module Data.Histogram.Fill ( -- * Builder type class HistBuilder(..) -- ** Operators , (<<-) , (<<-|) , (<<?) , (<<-$) , (-<<) -- * Histogram builders -- ** Stateful , HBuilderM(..) , feedOne , freezeHBuilderM -- ** Stateless , HBuilder(HBuilder) , toHBuilderST , toHBuilderIO , toHBuilderM -- * Histogram constructors -- ** Using unboxed vectors , module Data.Histogram.Bin , mkSimple , mkWeighted , mkMonoidal , mkFoldBuilder -- ** Using generic vectors , mkSimpleG , mkWeightedG , mkMonoidalG , mkFoldBuilderG -- ** Pure fold , mkFolder -- ** Generic constructors , mkStatefulBuilder -- * Fill histograms , fillBuilder , fillBuilderVec -- * Auxillary functions -- $auxillary , forceInt , forceDouble , forceFloat -- * Examples -- $examples -- * Deprecated functions , joinHBuilder , joinHBuilderM , treeHBuilderM , treeHBuilder ) where import Control.Applicative import Control.Monad (when,liftM,liftM2) import Control.Monad.ST import Control.Monad.Primitive import Data.PrimRef import Data.Monoid (Monoid(..)) import Data.Vector.Unboxed (Unbox) import qualified Data.Vector.Generic as G import qualified Data.Foldable as F import qualified Data.Traversable as F import Data.Histogram import qualified Data.Histogram.Generic as H import Data.Histogram.Bin import Data.Histogram.ST ---------------------------------------------------------------- -- Type class ---------------------------------------------------------------- -- | Type class for stateful accumulators. In this module they are -- called builders. Every builder is parametrized by two -- types. First one is type of values which are fed to accumulator -- and second one is type of values which could be extracted from -- it. -- -- Every instance of 'HBuilder' should be instance of 'Functor' too -- and satisfy 'fmap' == 'modifyOut'. class HistBuilder h where -- | Apply function to output of histogram. modifyOut :: (b -> b') -> h a b -> h a b' -- | Change input of builder by applying function to it. modifyIn :: (a' -> a) -> h a b -> h a' b -- | Put all values in container into builder fromContainer :: (forall m. Monad m => (a -> m ()) -> f a -> m ()) -- ^ @mapM_@ function for container -> h a b -> h (f a) b -- | Add cut to histogram. Value would be putted into histogram -- only if condition is true. addCut :: (a -> Bool) -> h a b -> h a b -- | Modify input of builder (<<-) :: HistBuilder h => h a b -> (a' -> a) -> h a' b (<<-) = flip modifyIn {-# INLINE (<<-) #-} -- | Modify input of builder to use composite input (<<-|) :: (HistBuilder h, F.Foldable f) => h a b -> (a' -> f a) -> h a' b h <<-| f = fromContainer F.mapM_ h <<- f {-# INLINE (<<-|) #-} -- | Add cut for input (<<?) :: HistBuilder h => h a b -> (a -> Bool) -> h a b (<<?) = flip addCut {-# INLINE (<<?) #-} -- | Apply function which modify builder (<<-$) :: HistBuilder h => h a b -> (h a b -> h a' b) -> h a' b h <<-$ f = f h {-# INLINE (<<-$) #-} -- | Modify output of histogram. In fact it's same as '<$>' but have opposite fixity (-<<) :: HistBuilder h => (b -> b') -> h a b -> h a b' (-<<) = modifyOut {-# INLINE (-<<) #-} -- Fixity of operator infixl 5 <<- infixl 5 <<-| infixl 5 <<? infixl 5 <<-$ infixr 4 -<< -- $examples -- -- All examples will make use of operators to create builders. It's -- possible to avoid their use but operators offer clear notation and -- compose nicely in pipeline. Also note that data flows from right to -- left as with '.' operator. -- -- First example just counts ints in in [0..4] inclusive range. -- 'fillBuilder' is used to put all values into accumulator. -- -- > ghci> let h = forceInt -<< mkSimple (BinI 0 4) -- > ghci> fillBuilder h [0,0,0,1,1,2,3,4,4,4] -- > # Histogram -- > # Underflows = 0 -- > # Overflows = 0 -- > # BinI -- > # Low = 0 -- > # High = 4 -- > 0 3 -- > 1 2 -- > 2 1 -- > 3 1 -- > 4 3 -- -- More involved example only accept even numbers. Filtering could be -- achieved with either 'addCut' or '<<?' operator. -- -- > forceInt -<< mkSimple (BinI 0 4) <<? even -- -- Although for example above same result could be acheved by -- filtering of input it doesn't work when multiple histograms with -- different cuts are filled simultaneously. -- -- Next example illustrate use of applicative interface. Here two -- histograms are filled at the same time. First accept only even -- numbers and second only odd ones. Results are put into the tuple. -- -- > (,) <$> -- > (forceInt -<< mkSimple (BinI 0 4) <<? even) -- > (forceInt -<< mkSimple (BinI 0 4) <<? odd) -- -- Another approach is to use 'F.sequenceA' to simultaneously fill -- list (or any other 'Travesable'). -- -- > Data.Traversable.sequenceA [ -- > forceInt -<< mkSimple (BinI 0 4) <<? even -- > , forceInt -<< mkSimple (BinI 0 4) <<? odd -- > ] -- -- If one wants to collect result from many histograms he can take an -- advantage of 'Monoid' instance of 'HBuilder'. Example below -- concatenates string outputs of individual histograms. -- -- > mconcat [ -- > show . forceInt -<< mkSimple (BinI 0 4) <<? even -- > , show . forceInt -<< mkSimple (BinI 0 4) <<? odd -- > ] ---------------------------------------------------------------- -- Monadic builder ---------------------------------------------------------------- -- | Stateful histogram builder. Adding value to builder could be done -- with 'feedOne' and result could be extracted with -- 'freezeHBuilderM'. -- -- There are two ways to obtain stateful builder. First and -- recommended is to thaw 'HBuilder' using 'toHBuilderIO' or -- 'toHBuilderST'. Second is to use 'mkStatefulBuilder'. data HBuilderM m a b = HBuilderM { hbInput :: a -> m () , hbOutput :: m b } -- | Builders modified using 'HistBuilder' API will share same buffer. instance Monad m => HistBuilder (HBuilderM m) where modifyIn f h = h { hbInput = hbInput h . f } addCut f h = h { hbInput = \x -> when (f x) (hbInput h x) } fromContainer fmapM_ h = h { hbInput = fmapM_ (hbInput h) } modifyOut f h = h { hbOutput = f `liftM` hbOutput h } instance Monad m => Functor (HBuilderM m a) where fmap = modifyOut instance Monad m => Applicative (HBuilderM m a) where pure x = HBuilderM { hbInput = const $ return () , hbOutput = return x } f <*> g = HBuilderM { hbInput = \a -> hbInput f a >> hbInput g a , hbOutput = do a <- hbOutput f b <- hbOutput g return (a b) } instance (Monad m, Monoid b) => Monoid (HBuilderM m a b) where mempty = HBuilderM { hbInput = \_ -> return () , hbOutput = return mempty } mappend h1 h2 = mappend <$> h1 <*> h2 mconcat = fmap mconcat . F.sequenceA {-# INLINE mempty #-} {-# INLINE mconcat #-} -- | Put one item into histogram feedOne :: Monad m => HBuilderM m a b -> a -> m () feedOne = hbInput {-# INLINE feedOne #-} -- | Extract result from histogram builder. It's safe to call this -- function multiple times and mutate builder afterwards. freezeHBuilderM :: Monad m => HBuilderM m a b -> m b freezeHBuilderM = hbOutput {-# INLINE freezeHBuilderM #-} ---------------------------------------------------------------- -- Stateless ---------------------------------------------------------------- -- | Wrapper around stateful histogram builder. It is much more -- convenient to work with than 'HBuilderM'. newtype HBuilder a b = HBuilder (forall m. PrimMonad m => m (HBuilderM m a b)) -- | Convert builder to stateful builder in primitive monad toHBuilderM :: PrimMonad m => HBuilder a b -> m (HBuilderM m a b) {-# INLINE toHBuilderM #-} toHBuilderM (HBuilder hb) = hb -- | Convert builder to stateful builder in ST monad toHBuilderST :: HBuilder a b -> ST s (HBuilderM (ST s) a b) {-# INLINE toHBuilderST #-} toHBuilderST = toHBuilderM -- | Convert builder to builder in IO monad toHBuilderIO :: HBuilder a b -> IO (HBuilderM IO a b) {-# INLINE toHBuilderIO #-} toHBuilderIO = toHBuilderM instance HistBuilder (HBuilder) where modifyIn f (HBuilder h) = HBuilder (modifyIn f `liftM` h) addCut f (HBuilder h) = HBuilder (addCut f `liftM` h) fromContainer fmapM_ (HBuilder h) = HBuilder (fromContainer fmapM_ `liftM` h) modifyOut f (HBuilder h) = HBuilder (modifyOut f `liftM` h) instance Functor (HBuilder a) where fmap = modifyOut instance Applicative (HBuilder a) where pure x = HBuilder (return $ pure x) (HBuilder f) <*> (HBuilder g) = HBuilder $ liftM2 (<*>) f g instance Monoid b => Monoid (HBuilder a b) where mempty = HBuilder (return mempty) mappend h g = mappend <$> h <*> g mconcat = fmap mconcat . F.sequenceA {-# INLINE mempty #-} {-# INLINE mappend #-} {-# INLINE mconcat #-} ---------------------------------------------------------------- -- Constructors ---------------------------------------------------------------- -- | Create builder. Bin content will be incremented by 1 for each -- item put into histogram mkSimple :: (Bin bin, Unbox val, Num val ) => bin -> HBuilder (BinValue bin) (Histogram bin val) mkSimple = mkSimpleG {-# INLINE mkSimple #-} -- | Create builder. Bin content will incremented by weight supplied -- for each item put into histogram mkWeighted :: (Bin bin, Unbox val, Num val ) => bin -> HBuilder (BinValue bin,val) (Histogram bin val) mkWeighted = mkWeightedG {-# INLINE mkWeighted #-} -- | Create builder. New value wil be mappended to current content of -- a bin for each item put into histogram mkMonoidal :: (Bin bin, Unbox val, Monoid val ) => bin -> HBuilder (BinValue bin,val) (Histogram bin val) mkMonoidal = mkMonoidalG {-# INLINE mkMonoidal #-} -- | Create most generic histogram builder. mkFoldBuilder :: (Bin bin, Unbox val) => bin -- ^ Binning algorithm -> val -- ^ Initial value -> (val -> a -> val) -- ^ Folding function -> HBuilder (BinValue bin, a) (Histogram bin val) {-# INLINE mkFoldBuilder #-} mkFoldBuilder = mkFoldBuilderG -- | Create builder. Bin content will be incremented by 1 for each -- item put into histogram mkSimpleG :: (Bin bin, G.Vector v val, Num val ) => bin -> HBuilder (BinValue bin) (H.Histogram v bin val) mkSimpleG bin = HBuilder $ do acc <- newMHistogram 0 bin return HBuilderM { hbInput = \x -> fill acc x (+) 1 , hbOutput = freezeHist acc } {-# INLINE mkSimpleG #-} -- | Create builder. Bin content will incremented by weight supplied -- for each item put into histogram mkWeightedG :: (Bin bin, G.Vector v val, Num val ) => bin -> HBuilder (BinValue bin,val) (H.Histogram v bin val) mkWeightedG bin = mkFoldBuilderG bin 0 (+) {-# INLINE mkWeightedG #-} -- | Create builder. New value wil be mappended to current content of -- a bin for each item put into histogram mkMonoidalG :: (Bin bin, G.Vector v val, Monoid val ) => bin -> HBuilder (BinValue bin,val) (H.Histogram v bin val) mkMonoidalG bin = mkFoldBuilderG bin mempty mappend {-# INLINE mkMonoidalG #-} -- | Create most generic histogram builder. mkFoldBuilderG :: (Bin bin, G.Vector v val) => bin -- ^ Binning algorithm -> val -- ^ Initial value -> (val -> a -> val) -- ^ Folding function -> HBuilder (BinValue bin, a) (H.Histogram v bin val) {-# INLINE mkFoldBuilderG #-} mkFoldBuilderG bin x0 f = HBuilder $ do acc <- newMHistogram x0 bin return HBuilderM { hbInput = \(!x,!w) -> fill acc x f w , hbOutput = freezeHist acc } -- | Create histogram builder which just does ordinary pure fold. It -- is intended for use when some fold should be performed together -- with histogram filling mkFolder :: b -> (a -> b -> b) -> HBuilder a b {-# INLINE mkFolder #-} mkFolder a f = HBuilder $ do ref <- newPrimRef a return HBuilderM { hbInput = \x -> modifyPrimRef' ref (f x) , hbOutput = readPrimRef ref } -- | Create stateful histogram builder. Output function should be safe -- to call multiple times and builder could be modified afterwards. -- So functions like @unsafeFreeze@ from @vector@ couldn't be used. mkStatefulBuilder :: Monad m => (a -> m ()) -- ^ Add value to accumulator -> m b -- ^ Extract result from accumulator -> HBuilderM m a b {-# INLINE mkStatefulBuilder #-} mkStatefulBuilder = HBuilderM ---------------------------------------------------------------- -- Actual filling of histograms ---------------------------------------------------------------- -- | Fill histogram builder. fillBuilder :: F.Foldable f => HBuilder a b -> f a -> b fillBuilder hb xs = runST $ do h <- toHBuilderST hb F.mapM_ (feedOne h) xs freezeHBuilderM h -- | Fill histogram builder. fillBuilderVec :: G.Vector v a => HBuilder a b -> v a -> b {-# INLINE fillBuilderVec #-} fillBuilderVec hb = \vec -> runST $ do h <- toHBuilderST hb G.mapM_ (feedOne h) vec freezeHBuilderM h ---------------------------------------------------------------- -- $auxillary -- -- In some cases builder constructors do not constrain output type -- enough. Output type is still parametric in value type of histogram. -- Functions below are just 'id' function with more restrictive -- signature. -- -- In example below 'forceInt' used to fix type of histogram to -- 'Histogram BinI Int'. Without it compiler cannot infer type of -- intermediate histogram. -- -- > show . forceInt -<< mkSimple (BinI 1 10) forceInt :: H.Histogram v bin Int -> H.Histogram v bin Int forceInt = id forceDouble :: H.Histogram v bin Double -> H.Histogram v bin Double forceDouble = id forceFloat :: H.Histogram v bin Float -> H.Histogram v bin Float forceFloat = id ---------------------------------------------------------------- -- Deprecated ---------------------------------------------------------------- -- | Join histogram builders in container joinHBuilderM :: (F.Traversable f, Monad m) => f (HBuilderM m a b) -> HBuilderM m a (f b) joinHBuilderM = F.sequenceA {-# INLINE joinHBuilderM #-} {-# DEPRECATED joinHBuilderM "Use Data.Traversable.sequenceA instead" #-} -- | Apply functions to builder treeHBuilderM :: (Monad m, F.Traversable f) => f (HBuilderM m a b -> HBuilderM m a' b') -> HBuilderM m a b -> HBuilderM m a' (f b') treeHBuilderM fs h = F.traverse ($ h) fs {-# INLINE treeHBuilderM #-} {-# DEPRECATED treeHBuilderM "Use Data.Traversable.traverse. treeHBuilderM fs h = F.traverse ($ h) fs" #-} -- | Join hitogram builders in container. joinHBuilder :: F.Traversable f => f (HBuilder a b) -> HBuilder a (f b) joinHBuilder = F.sequenceA {-# INLINE joinHBuilder #-} {-# DEPRECATED joinHBuilder "Use Data.Traversable.sequenceA instead" #-} -- | Apply function to builder treeHBuilder :: F.Traversable f => f (HBuilder a b -> HBuilder a' b') -> HBuilder a b -> HBuilder a' (f b') treeHBuilder fs h = F.traverse ($ h) fs {-# INLINE treeHBuilder #-} {-# DEPRECATED treeHBuilder "Use Data.Traversable.traverse. treeHBuilderM fs h = F.traverse ($ h) fs" #-}