comonad-random-0.1.0: Control/Comonad/Random.hs
{-# LANGUAGE GeneralizedNewtypeDeriving, Rank2Types #-}
-- | This module provides a comonadic interface to random values. In
-- some situations, this may be more natural than a monadic
-- approach.
module Control.Comonad.Random
( module Control.Comonad
, module System.Random
-- * Example
-- $Example
-- * Data Type
, Rand ()
-- ** Creation
, mkRand
, mkRandR
-- ** Transformations
, next
, left
, right
-- ** Convenience
, extracts
)
where
import Control.Applicative
import Control.Comonad
import Control.Comonad.Cofree
import Control.Functor.Pointed
import Data.Function
import System.Random hiding (next)
newtype Three a = Three (a, a, a)
fst3 :: Three a -> a
fst3 (Three (x, _, _)) = x
snd3 :: Three a -> a
snd3 (Three (_, y, _)) = y
thd3 :: Three a -> a
thd3 (Three (_, _, z)) = z
instance Functor Three where
fmap f (Three ~(x, y, z)) = Three (f x, f y, f z)
instance Applicative Three where
pure x = Three (x, x, x)
Three ~(f, g, h) <*> Three ~(x, y, z) = Three (f x, g y, h z)
-- | A memoized supply of values
newtype Rand a = Rand { unRand :: Cofree Three a }
deriving (Functor, Copointed, Comonad)
inRand :: (Cofree Three a -> Cofree Three b) -> (Rand a -> Rand b)
inRand = (Rand .) . (. unRand)
instance Applicative Rand where
pure x = let tree = cofree x $ Three (tree, tree, tree)
in Rand tree
Rand a <*> Rand b = let ~(f, fs) = runCofree a
~(x, xs) = runCofree b
in Rand . cofree (f x) . fmap unRand .
liftA2 (<*>) (fmap Rand fs) . fmap Rand $ xs
mkRandWith :: RandomGen g => (g -> (a, g)) -> g -> Rand a
mkRandWith f g = let ~(x, g') = f g
~(l, r) = split g
in Rand . cofree x . fmap (unRand . mkRandWith f) . Three $ (l, g', r)
-- | Create a comonadic generator from a 'RandomGen'.
mkRand :: (RandomGen g, Random a) => g -> Rand a
mkRand = mkRandWith random
-- | Create a comonadic generator from a 'RandomGen' where the values
-- are limited to a given range.
mkRandR :: (RandomGen g, Random a) => (a, a) -> g -> Rand a
mkRandR = mkRandWith . randomR
inner :: (forall b . Three b -> b) -> (Rand a -> Rand a)
inner f = inRand $ f . snd . runCofree
-- | Get the generator for the next value.
next :: Rand a -> Rand a
next = inner snd3
-- | Split the generator, returning the new left one.
left :: Rand a -> Rand a
left = inner fst3
-- | Split the generator, returning the new right one.
right :: Rand a -> Rand a
right = inner thd3
-- | Generate an infinite list of values by applying a function
-- repeatedly.
extracts :: (Rand a -> Rand a) -> Rand a -> [a]
extracts f = liftA2 (:) extract (extracts f . f)
{- $Example
This example is based on the example given in the MonadRandom
package.
@rolls@ is a supply of random dice rolls, picking a number between
1 and 6, inclusive.
> rolls :: RandomGen g => g -> Rand Int
> rolls = mkRandR (1, 6)
@extractN@ may be used to supply @n@ values from a @Rand@.
> extractN :: Int -> Rand a -> [a]
> extractN n = take n . extracts next
We can bootstrap the whole thing with a generator, giving us a pure
interface to the random values.
> main = print . extractN 2 . rolls =<< getStdGen
One potential gotcha with this library is that a top-level @Rand@
that is extracted deeply could result in a space leak due to the
memoization. It's a good idea to try not to hold on to @Rand@s for
longer than necessary.
-}