Allow MonadRandom interface for MWC-random #26

Open
dogirardo opened this Issue Aug 10, 2016 · 6 comments

Projects

None yet

6 participants

@dogirardo

We'd like to be able to define a MonadRandom instance for a newtype'd ST, using the same unsafePerformIO IORef trick as the IO instance, but with MWC-random's Gen rather than the System.Random Gen.
Fundamentally, the problem is that random et al require
This is sad because the interface should be exactly the same.
One solution is to eliminate the Random a requirement and fix a return type (Word32, Int,Double, etc), since all random generators fundamentally operate on these primitive types. Another solution is to make a multiparam version of MonadRandom parameterizing the generator type and/or return type.

I'd pick the first solution, but I'd like to know if anyone has a use-case for baking different randomness mechanisms into the Random typeclass, rather than just post applying an f :: Int -> A or somesuch

@byorgey
Owner
byorgey commented Nov 10, 2016 edited

Hi @dogirardo , sorry for not responding before now. I'm currently doing some major updates to MonadRandom, and would love to figure out a solution to this issue. For starters, though, I am not sure I completely understand what the problem is. You say "Fundamentally, the problem is that random et al require" but it seems like you did not complete that sentence. Could you please clarify? Thanks!

@jp-beaumont
jp-beaumont commented Nov 17, 2016 edited

To implement getRandom :: Random a => m a one needs access to a type that implements RandomGen to use when calling the methods from Random. mwc-random doesn't [and can't] implement RandomGen. One proposed solution is to modify MonadRandom so that getRandom :: m Word32. The second proposal generalizes this to

class MonadRandom m a where
    getRandom :: m a

Neither of these is as flexible or convenient as the current setup in cases where a MonadGen instance exists. A third alternative which is a strict generalization of the current MonadRandom would be

class MonadRandom m where
    type Random m a :: Constraint
    getRandom :: Random m a => m a

This would allow each MonadRandom instance to specify the subset of types it is capable of generating. I would favour this third solution but it requires the ConstraintKinds and TypeFamilies extensions and I don't know if there is any policy on using/avoiding certain extensions in MonadRandom.

Edit: Thinking on this some more, the third solution would only be backward compatible in contexts where m is known. Generic functions would require additional Random m a constraints for each type of random value they need to generate. A less than ideal situation. A more complete solution might look something like

type RandomPrimitives m
    = ( Random m Int, Random m Int8, Random m Int16, Random m Int32, Random m Int64
      , Random m Word, Random m Word8, Random m Word16, Random m Word32, Random m Word64
      , Random m Float, Random m Double )

class RandomPrimitives m => MonadRandom m where
    type Random m :: * -> Constraint
    getRandom :: (Random m a) => m a

type MonadRandomGen m
    = (MonadRandom m, Random m ~ System.Random.Random)

type MonadRandomMWC m
    = (MonadRandom m, Random m ~ System.Random.MWC.Variate)

This would be backward compatible with code that only needs to generate primitive types. Code using other types would need to replace uses of MonadRandom with MonadRandomGen. Doing so would prevent that code from being used with mwc-random but that's no worse than the current situation.

The downside to this is the complexity, the need for questionable/scary extensions (UndecidableInstances and UndecidableSuperClasses), and potential compatibility issues with older GHC versions. It may not be worth the cost.

@Shimuuar

Main difference between PRNGs from random and mwc-random is their state. State of PRNG from random is pure value and could be threaded in state monad. And state of mwc-random is mutable buffer. It's large: 258 Word32 so for performance reasons it's modified in place.

Therefore different RandTs are needed for different generators. StateT Gen m for ones with pure state and MonadIO/St m ⇒ ReaderT Gen m for ones with mutable state.

@ygale
ygale commented Nov 17, 2016 edited

How about something like this:

newtype PrimMonad m => RandMwcT m a = RandMwcT (ReaderT (Gen (PrimState m)) m a)
  deriving ( Monad, {- lots of stuff -} )

class Monad m => RandomM m a where
  getRandomM :: m a
  -- etc., like current MonadRandom but without the Random constraints

instance (MonadRandom m, Random a) => RandomM m a where
  getRandomM = getRandom
  -- etc.

instance (PrimMonad m, Variate a) => RandomM (RandMwcT m) a where
  getRandomM = RandMwcT $ asks uniform
  getRandomMR = RandMwcT $ asks uniformR
  -- etc.

This is a non-breaking change. We are only adding a new type and and a class, not touching the current RandT and MonadRandom class.

Note that RandomM is not a sub-class of Monad, unlike MonadRandom. You use it as a constraint with two type parameters. Types with this constraint are then compatible with both System.Random and mwc-random if your monad is an instance of PrimMonad and your value is an instance of both Random and Variate.

EDIT: Generalized the instance for RandT to include any MonadRandom instance. Fixed a typo.

@dogirardo

Ahh sorry for the omission/typo, but @jp-beaumont sums up what I was going to say

@idontgetoutmuch
idontgetoutmuch commented Nov 29, 2016 edited

You can already do this in random-fu. NB I am absolutely not saying it is better but just trying to inform debate to reach a better place.

{-# OPTIONS_GHC -Wall #-}

import Data.Random
import Control.Monad.Reader
import Data.Random.Source.MWC
import qualified Control.Monad.Random as M
import qualified Data.Vector as V

die :: (M.RandomGen g) => M.Rand g Int
die = M.getRandomR (1,6)

dice :: (M.RandomGen g) => Int -> M.Rand g [Int]
dice n = sequence (replicate n die)

die' :: MonadRandom m => m Int
die' = sample (uniform 1 6)

dice' :: MonadRandom m => Int -> m [Int]
dice' n = sequence (replicate n die')

main :: IO ()
main = do
  seed <- initialize (V.fromList [2])
  values' <- runReaderT (dice' 10000) seed
  putStrLn (show $ sum values')
  values <- M.evalRandIO (dice 10000)
  putStrLn (show $ sum values)
*Main> :i MonadRandom
class Monad m => MonadRandom (m :: * -> *) where
  Data.Random.Internal.Source.getRandomPrim :: random-source-0.3.0.7:Data.Random.Source.Internal.Prim.Prim
                                                 t
                                               -> m t
  Data.Random.Internal.Source.getRandomWord8 :: m GHC.Word.Word8
  Data.Random.Internal.Source.getRandomWord16 :: m GHC.Word.Word16
  Data.Random.Internal.Source.getRandomWord32 :: m GHC.Word.Word32
  Data.Random.Internal.Source.getRandomWord64 :: m GHC.Word.Word64
  Data.Random.Internal.Source.getRandomDouble :: m Double
  Data.Random.Internal.Source.getRandomNByteInteger :: MonadRandom
                                                         m =>
                                                       Int -> m Integer
  	-- Defined in ‘Data.Random.Internal.Source’
instance MonadRandom (RVarT n) -- Defined in ‘Data.RVar’
instance MonadRandom IO -- Defined in ‘Data.Random.Source.IO’
instance (Control.Monad.Primitive.PrimMonad m0,
          s0 ~ Control.Monad.Primitive.PrimState m0) =>
         MonadRandom (ReaderT (Gen s0) m0)
  -- Defined in ‘Data.Random.Source.MWC’
*Main> :i M.MonadRandom
class Monad m => M.MonadRandom (m :: * -> *) where
  M.getRandom :: M.Random a => m a
  M.getRandoms :: M.Random a => m [a]
  M.getRandomR :: M.Random a => (a, a) -> m a
  M.getRandomRs :: M.Random a => (a, a) -> m [a]
  {-# MINIMAL getRandom, getRandoms, getRandomR, getRandomRs #-}
  	-- Defined in ‘Control.Monad.Random.Class’
instance M.MonadRandom m => M.MonadRandom (ReaderT r m)
  -- Defined in ‘Control.Monad.Random’
instance (Monad m, M.RandomGen g) => M.MonadRandom (M.RandT g m)
  -- Defined in ‘Control.Monad.Random’
instance M.MonadRandom IO -- Defined in ‘Control.Monad.Random’
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment