MonadFix instance for Rand monad

477 views Asked by At

I would like to generate infinite stream of numbers with Rand monad from System.Random.MWC.Monad. If only there would be a MonadFix instance for this monad, or instance like this:

instance (PrimMonad m) => MonadFix m where
     ...

then one could write:

runWithSystemRandom (mfix (\ xs -> uniform >>= \x -> return (x:xs)))

There isn't one though.

I was going through MonadFix docs but I don't see an obvious way of implementing this instance.

3

There are 3 answers

2
Chris Kuklewicz On BEST ANSWER

A question: how do you wish to generate your initial seed?

The problem is that MWS is built on the "primitive" package which abstracts only IO and strict (Control.Monad.ST.ST s). It does not also abstract lazy (Control.Monad.ST.Lazy.ST s).

Perhaps one could make instances for "primitive" to cover lazy ST and then MWS could be lazy.

UPDATE: I can make this work using Control.Monad.ST.Lazy by using strictToLazyST:

module Main where

import Control.Monad(replicateM)
import qualified Control.Monad.ST as S
import qualified Control.Monad.ST.Lazy as L
import qualified System.Random.MWC as A

foo :: Int -> L.ST s [Int]
foo i = do rest <- foo $! succ i
           return (i:rest)

splam :: A.Gen s -> S.ST s Int
splam = A.uniformR (0,100)

getS :: Int -> S.ST s [Int]
getS n = do gen <- A.create
            replicateM n (splam gen)

getL :: Int -> L.ST s [Int]
getL n = do gen <- createLazy
            replicateM n (L.strictToLazyST (splam gen))

createLazy :: L.ST s (A.Gen s)
createLazy = L.strictToLazyST A.create

makeLots :: A.Gen s -> L.ST s [Int]
makeLots gen = do x <- L.strictToLazyST (A.uniformR (0,100) gen)
                  rest <- makeLots gen
                  return (x:rest)

main = do
  print (S.runST (getS 8))
  print (L.runST (getL 8))
  let inf = L.runST (foo 0) :: [Int]
  print (take 10 inf)
  let inf3 = L.runST (createLazy >>= makeLots) :: [Int]
  print (take 10 inf3)
2
Heatsink On

You can write a MonadFix instance. However, the code will not generate an infinite stream of distinct random numbers. The argument to mfix is a function that calls uniform exactly once. When the code is run, it will call uniform exactly once, and create an infinite list containing the result.

You can try the equivalent IO code to see what happens:

import System.Random
import Control.Monad.Fix
main = print . take 10 =<< mfix (\xs -> randomIO >>= (\x -> return (x : xs :: [Int])))

It seems that you want to use a stateful random number generator, and you want to run the generator and collect its results lazily. That isn't possible without careful use of unsafePerformIO. Unless you need to produce many random numbers quickly, you can use a pure RNG function such as randomRs instead.

0
Petr On

(This would be better suited as a comment to Heatsink's answer, but it's a bit too long.)

MonadFix instances must adhere to several laws. One of them is left shrinking/thightening:

mfix (\x -> a >>= \y -> f x y)  =  a >>= \y -> mfix (\x -> f x y)

This law allows to rewrite your expression as

mfix (\xs -> uniform >>= \x -> return (x:xs))
= uniform >>= \x -> mfix (\xs -> return (x:xs))
= uniform >>= \x -> mfix (return . (x :))

Using another law, purity mfix (return . h) = return (fix h), we can further simplify to

= uniform >>= \x -> return (fix (x :))

and using the standard monad laws and rewriting fix (x :) as repeat x

= liftM (\x -> fix (x :)) uniform
= liftM repeat uniform

Therefore, the result is indeed one invocation of uniform and then just repeating the single value indefinitely.