I want to sample a Normal distribution with given mean and standard deviation. I know how to do that in various contexts like Data.Random.Rvar or Data.Random.MonadRandom. However, the context of my function is Control.Monad.MonadRandom, and I would like to keep it that way as my whole project uses Control.Monad.MonadRandom.
Is there any way to do so, and could you help me do that?
Here is how the code looks like. Pattern is just an alias for Data.Vector Double and Weights is an alias for Data.Vector (Data.Vector Double) (ie a matrix)
train :: MonadRandom m => [Pattern] -> Int -> m Weights
train pats nr_hidden = do
ws_start <- ws_start''
foldM updateWS ws_start pats
where ws_start' = take p (repeat $ take nr_hidden $ repeat $ (normal 0.0 0.01))
ws_start'' = vector2D <$> (sequence $ map sequence ws_start')
p = length pats
Thank you.
How to use a Data.Random.RVar
inside a Control.Monad.MonadRandom
?
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad.Random as CMR
import Data.Random as DR
import Data.Word (Word32)
gimmeRandom :: forall m . CMR.MonadRandom m => m Int
gimmeRandom = do
r <- runRVar (uniform 0 100) (getRandom :: m Word32)
return r
Effectively, you want to run a Monad inside a formally different Monad with similar semantics.
Data.Random.MonadRandom
and Control.Monad.Random
are formally different because they are defined indepently in different places, and none is an instance of the other (there is no instance DR.MonadRandom m => CMR.MonadRandom m
or the other way around).Let us say you have some code in a Control.Monad.Random
interface:
import Control.Monad.Random as CMR
gimmeRandom :: CMR.MonadRandom m => m Int
gimmeRandom = do
r <- getRandomR (0, 100)
return r
We could run this like evalRand gimmeRandom StdGen
which gives us an Int
.
Now instead of getRandomR
, you want to use one of the many available distributions provided by Data.Random
.
For this example, we will try to replace getRandomR (0, 100)
by uniform 0 100 :: RVar Int
. How to we get the Int
out of that RVar Int
in our CMR.MonadRandom
environment?
We want to run the RVar
monad, for which we will probably have to provide a random number source, as the semantic suggests. We are looking for a monad-escaping function like evalRand for CMR. These escaping functions have type m a -> someStuffNeededToRunTheMonad -> a
.
In the docs about RVar, there is an example:
-- In a monad, using a RandomSource:
runRVar (uniform 1 100) DevRandom :: IO Int
Let's check runRVar
:
runRVar :: RandomSource m s => RVar a -> s -> m a
Yes, that is a kind of escaping function: Given an RVar
and a source for random numbers, it returns us the random result of the RVar
inside our own monad m
. This, however, requires, that there is an instance RandomSource m s
that says that s
is a randomness source for our monad m
. Let's look for that instance.
What is our monad m
? We want to run the RVar
in gimmeRandom
, so the monad is CMR.MonadRandom m => m
(all monads that implement CMR.MonadRandom
). What is the randomness source s
? No clue yet. Let us look in the docs which RandomSource
instances exist:
RandomSource IO DevRandom
...
Monad m0 => RandomSource m0 (m0 Word32)
Monad m0 => RandomSource m0 (m0 Word64)
...
Aha! This says that any monad m0
is an instance of RandomSource
together with a value coming from this monad (e.g. m0 Word32
). This hold of course also for our monad CMR.MonadRandom
. We can also see that the s
, m0 Word32
, must be the random value generated by the randomness source.
What should we pass in as the s
in runRVar (uniform 0 100) s
? Something that generates random numbers in our monad, something of type CMR.MonadRandom m => m Word32
. What is the CMR
function to generate arbitrary things, e.g. some Word32
? getRandom. So basically we want to write:
gimmeRandom :: CMR.MonadRandom m => m Int
gimmeRandom = do
r <- runRVar (uniform 0 100) getRandom
return r
Hmm, that does not compile:
Could not deduce (RandomSource m (m0 a0))
arising from a use of `runRVar'
from the context (CMR.MonadRandom m)
bound by the type signature for
gimmeRandom :: CMR.MonadRandom m => m Int
RandomSource m (m0 a0)
? That is weird, the m
and the m0
seem to be recognised as different monads by the compiler; we want them to be the same, as in RandomSource m0 (m0 Word64)
.
Let us put the full signature into that place:
r <- runRVar (uniform 0 100) (getRandom :: CMR.MonadRandom m => m Word32)
Still the same error. This is because the m
in that type signature, is really any monad implementing CMR.MonadRandom
, not necessarily the MonadRandom
in our gimmeRandom
type signature.
(This is the same concept of shadowing as in lambda terms (\x -> (\x -> f x))
where the inner \x
is the one used in f x
; or in first-order logic like ∀x . F(x) → ∀x . G(x)
, where the x
in G(x)
is the innermost defined one and need not be the same, not even of the same type, as the one in the outer ∀x
; or really in any other programming language with variable hiding/shadowing in inner scopes - just that here it is type variable shadowing).
So the only thing that we have to do is to tell the compiler that in the getRandom
invocation, we don't want that to be for any MonadRandom
, but for exactly that MonadRandom m
that we have in the gimmeRandom
type signature.
We can do that using the ScopedTypeVariables
extension:
{-# LANGUAGE ScopedTypeVariables #-}
[...]
gimmeRandom :: forall m . CMR.MonadRandom m => m Int
gimmeRandom = do
r <- runRVar (uniform 0 100) (getRandom :: m Word32)
return r
This makes that the m
in getRandom :: m ...
is to be chosen exactly that CMR.MonadRandom m
from the top-level type signature.
This does compile and the problem is solved: We can use distributions from Data.Random
in code using the MonadRandom
interface. We could easily replace the uniform
by another distribution.
To summarise, we have
runRVar
)runRVar (uniform 0 100) getRandom
)getRandom
shall use.If you are wondering why we chose Word32
somewhat arbitrarily from the instances we can pick from, we just have to give the randomness source in some form, and Word32 is one of the things Data.Random
takes as input for generating other random stuff.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With