{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
{-# OPTIONS -Wall -Werror #-}

{- |
Portability  : non-portable (multi-parameter type classes, undecidable instances)
License : BSD3

A lazy monad for random-number generation.  This monad allows, for example,
computation of infinite random lists.

This monad respects the interface defined by 
'Control.Monad.Random.Class.MonadRandom'.

A monadic computation is one that consumes random values.
The bind operation works like the 'Gen' monad in 'Test.QuickCheck':
it does not thread the random seed; instead it *splits* the random seed.

-}

module Control.Monad.LazyRandom 
  ( module System.Random
  , module Control.Monad.Random.Class
  , evalRand, runRand, evalRandIO
  , Rand
  )
where
  
import Control.Monad.Random.Class

--------------------------------------------------------------------------
-- imports

import System.Random
  ( Random(..)
  , RandomGen(..)
  , StdGen, mkStdGen, getStdRandom
  )

import Control.Monad
  ( ap
  )

import Control.Applicative
  ( Applicative(..)
  )

--------------------------------------------------------------------------
-- ** Based on quickcheck generator type

newtype Rand g a = MkRand (g -> a)
-- | A value of type 'Rand g a' is a monadic computation which, when
-- run, consumes random values from an applicative random-number generator 
-- of type 'g' and produces a result of type 'a'.

instance Functor (Rand g) where
  fmap f (MkRand h) = MkRand (f . h)

instance RandomGen g => Applicative (Rand g) where
  pure  = return
  (<*>) = ap

instance RandomGen g => Monad (Rand g) where
  return x = MkRand (const x)
  
  MkRand m >>= k =
    MkRand (\r ->
      let (r1,r2)  = split r
          MkRand m' = k (m r1)
       in m' r2
    )



-- | Evaluate a random computation using the generator @g@.  The
-- new @g@ is discarded.
evalRand :: (RandomGen g) => Rand g a -> g -> a
evalRand (MkRand f) g = f g
 
-- | Run a random computation using the generator @g@, returning the result
-- and a new generator.
runRand :: (RandomGen g) => Rand g a -> g -> (a, g)
runRand (MkRand f) g = (f g1, g2)
  where (g1, g2) = split g
 
-- | Evaluate a random computation in the IO monad, using the random number
-- generator supplied by 'System.Random.getStdRandom'.
evalRandIO :: Rand StdGen a -> IO a
evalRandIO m = getStdRandom (runRand m)



instance (RandomGen g) => MonadRandom (Rand g) where
    getRandom = MkRand $ fst . random
    getRandoms = MkRand $ randoms
    getRandomR range = MkRand $ fst . randomR range
    getRandomRs range = MkRand $ randomRs range

instance (RandomGen g) => MonadSplit g (Rand g) where
    getSplit = MkRand id  -- I think this has to be right ---NR