> module Data.Distribution (Distribution(..), Probability, probability, (), choose, (<~~), chooseMany) where > > import Data.Maybe (fromMaybe) > import Data.Functor.Extras ((<$$>)) > import Data.Ratio ((%)) > import Control.Monad.Random (evalRand, fromList) > import System.Random (RandomGen(..)) We represent probabilities as ratios of `Integer`s , with no strict bounds-checking: > type Probability = Rational Distribution represents a discrete probability distribution. More specifically: given some type parameter, a Distribution relates each value of that type with the probability of its occurrence. > type Distribution a = [(a, Probability)] `choose` takes a random seed and a Distribution, and produces a value according to that Distribution. > choose :: RandomGen g => g -> Distribution a -> a > choose seed = (`evalRand` seed) . fromList > > (<~~) :: RandomGen g => Distribution a -> g -> a > (<~~) = flip choose `chooseMany` takes a random seed and a Distribution, and produces an infinite list of values according to that Distribution. Exercise: why shouldn't this be implemented as `map choose . repeat . fromList`? > chooseMany :: RandomGen g => g -> Distribution a -> [a] > chooseMany seed = (`evalRand` seed) . sequence . repeat . fromList `probability` takes a Distribution and a value, and returns the likelihood of that value occurring per that distribution. Should a probability not be defined for that value, this likelihood is zero: > probability :: Eq a => a -> Distribution a -> Probability > probability = fromMaybe (0%1) <$$> lookup > > ( a -> Distribution a -> Probability > ( > (?>) :: Eq a => Distribution a -> a -> Probability > (?>) = flip probability