That’s right, I was rather surprised it could be used in such a different way.
After expanding my code from before into a fully-fledged Monad instance
Random Monad based on Infinitree
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE InstanceSigs #-}
module Data.Infinitree.Random (build, Random(..), sample) where
import Data.Infinitree (Infinitree (Branch))
import qualified System.Random (RandomGen, Random)
import qualified System.Random as Random hiding (Random)
import qualified Data.Infinitree as Infinitree
newtype Random random result = Random (Infinitree random -> result)
deriving stock (Functor)
instance Applicative (Random random) where
pure :: a -> Random random a
pure a = Random $ const a
(<*>) :: Random random (a -> b) -> Random random a -> Random random b
(<*>) (Random makeF) (Random makeA) = Random $ \ (Branch left _ right) -> let
f = makeF left
a = makeA right
in f a
instance Monad (Random random) where
(>>=) :: Random random a -> (a -> Random random b) -> Random random b
(>>=) (Random makeA) f = Random $ \ (Branch left _ right) -> let
a = makeA left
(Random makeB) = f a
in makeB right
sample :: Random result result
sample = Random $ \ (Branch _ x _) -> x
-- | Build an infinitree of random values, this hides the used generator
--
-- Name conflicts force to write this out
build :: (System.Random.RandomGen g, System.Random.Random a) => g -> Infinitree a
build generator = let
(leaf, generator') = Random.random generator
(generatorLeft, generatorRight) = Random.split generator'
in Infinitree.Branch (build generatorLeft) leaf (build generatorRight)
I think that it will be inconvenient unless the program only every needs to generate Random values of a single type.
The typeclass instances I wrote also discard a lot of computation, since the generators in the branches strictly depend on the value put in the leaf. The value must be computed but may never be used.
To me, a state monad over the Generator seems preferable, like this
State MonadRandom
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE TupleSections #-}
module MonadRandom (MonadRandom, choose, run, useRandom, select, range) where
import System.Random (StdGen)
import qualified System.Random as Random
import Data.Bool (bool)
import Data.Vector.Strict (Vector)
import qualified Data.Vector.Strict as Vector
newtype MonadRandom a = MonadRandom (StdGen -> (a, StdGen))
deriving Functor
instance Applicative MonadRandom where
pure :: a -> MonadRandom a
pure x = MonadRandom (x, )
(<*>) :: MonadRandom (a -> b) -> MonadRandom a -> MonadRandom b
(<*>) (MonadRandom makeF) (MonadRandom makeX) = MonadRandom $ \ generator -> let
(f, gen') = makeF generator
(x, gen'') = makeX gen'
in (f x, gen'')
instance Monad MonadRandom where
(>>=) :: MonadRandom a -> (a -> MonadRandom b) -> MonadRandom b
(>>=) (MonadRandom makeX) f = MonadRandom $ \ generator -> let
(x, gen') = makeX generator
(MonadRandom makeB) = f x
in makeB gen'
useRandom :: (StdGen -> (x, StdGen)) -> MonadRandom x
useRandom f = MonadRandom $ \ generator -> f generator
run :: StdGen -> MonadRandom a -> a
run gen (MonadRandom f) = fst (f gen)
This one also threads the generator strictly through all computations, but it doesn’t have to eagerly evaluate random values that will never be used.
One Advantage the Tree based approach might have is that it is easily parallelizable.
(Edit: remove functions not needed here from the copied MonadRandom implementation)