```{- |
This library provides tools for doing single parameter Bayesian inference.

Example ghci use:

> # Generate a discrete simulation of the uniform prior for the bias
> # of a single coin on [0,1] using a grid of 100 points.
> let prior = uniformPrior 100
> # Update the prior as if we flipped the coin 100 times and got 25 heads.
> let posterior = coins prior 25 75
> # Optionally, import Graphics.Gnuplot.Simple and convert posterior
> # to a list of tuples for plotting using listify
> :m + Graphics.Gnuplot.Simple
> plotList [] (listify posterior)
> # As you'd expect, nearly all of the probability mass is concentrated
> # between a bias of 0.2 and 0.3
-}

module Numeric.Learn where

import Data.Ratio
import qualified Data.Map as Map

type Prob = Ratio Integer

newtype Distro a = Distro { runDistro :: Map.Map a Prob }

data Result = Positive | Negative

{- |
Takes a list of elements and generates a uniform Distro over them.
-}
uniformPDF :: (Ord k) =>  [k] -> Distro k
uniformPDF xs = Distro \$ foldl (\thisMap elt ->
Map.insert elt (1 % len) thisMap) Map.empty xs
where len = fromIntegral (length xs)

{- |
Generates a simulated uniform distro over [0,1] with user supplied granularity.
-}
uniformPrior :: Integer -> Distro Prob
uniformPrior granularity = uniformPDF xs
where xs = map ( % granularity) [0..granularity]

{- |
Updates a distro with a new trial that either succeeded or failed.
-}
singleCoin :: Distro Prob -> Result -> Distro Prob
singleCoin (Distro probs) result = Distro \$ Map.mapWithKey (update result) probs
where update Positive = \pE1gHi pHi -> (pE1gHi / pE1) * pHi
update Negative = \pE1gHi pHi -> ((1 - pE1gHi) / (1 - pE1)) * pHi
pE1             = Map.foldWithKey (\k a acc -> acc + k * a) 0 probs

{- |
Updates a distro with many trials, all of which either succeeded or failed.
-}
coins :: Distro Prob -> Int -> Int -> Distro Prob
coins distro heads tails = foldl (\thisDistro result ->
singleCoin thisDistro result) distro results
where results = (take heads \$ repeat Positive) ++ (take tails \$ repeat Negative)

{- |
Convert a Distro to a list of tuples for easier plotting.
-}
listify :: Distro k -> [(k, Prob)]
listify = Map.toList . runDistro
```