I wrote the code below, and now want to rewrite it using a Monad. And with do notation. I tried the do notation below but gets the output. It seems to have something to do with the tuples that I use binding for, which works in the original code:
seed::Int
seed = 2
giveList :: [Int]
giveList = [8,9,4,5,2]
generator = mkStdGen seed
giveRandomElement :: [a] -> a
giveRandomElement lst = lst !! rand where
n = length lst
(rand, _) = randomR (0,(n-1)) generator
random_response :: R.StdGen -> a -> [a] -> (a, R.StdGen)
random_response g true_answer answers =
let (val, g') = R.random g
(val', g'') = R.random g' :: (Bool, R.StdGen)
in if val then (true_answer, g'')
else (giveRandomElement answers , g'')
random_response_monad :: R.StdGen -> a -> [a] -> (a, R.StdGen)
random_response_monad g true_answer answers = do
(val, g') <- R.random g :: (Bool, R.StdGen)
(val', g'') <- R.random g' :: (Bool, R.StdGen)
if val then return (true_answer, g'')
else return (giveRandomElement answers, g'')
OUTPUT:
error:
* Couldn't match expected type `StdGen'
with actual type `(Bool, StdGen)'
* In the pattern: (val, g')
In a stmt of a 'do' block: (val, g') <- random g :: (Bool, StdGen)
In the expression:
do (val, g') <- random g :: (Bool, StdGen)
(val', g'') <- random g' :: (Bool, StdGen)
if val then
return (true_answer, g'')
else
return (giveRandomElement answers, g'')
(val, g') <- R.random g :: (Bool, R.StdGen)
I suspect that the type error I get is due to the <-
rather than the let
-binding. Is this correct, and where is my conceptual misunderstanding, and how can I fix the code?
Furthermore is it correct that what I am trying to do is rewriting it using a state-monad?
EDIT: Tried to answer below exercise suggestion
I have tried the below but get a parser error.
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
let random' = state $ randomR(0,1)
val <- random'
val' <- random'
if val then giveRandomElement answers
else giveRandomElement answers
Note that I changed both branches of if-statement to a call of same function and argument for testing.
CodePudding user response:
I think what you actually want by “rewrite it using a Monad” is indeed rewriting it in the state monad. It's no good using monad syntax if you don't have an actual monad type.
(It so happens that plain tuples can also act as a monad, but better ignore that, it's more confusing than helpful.)
The point of expressing specifically randomized calculations in a state monad is that it avoids the awkward and error-prone carrying around of explicit new versions of the random generator. I.e., you want to replace
let (val, g') = R.random g
(val', g'') = R.random g'
with simply
val <- random'
val' <- random'
To get there, let's first re-order the signature:
random_response :: a -> [a] -> (R.StdGen -> (a, R.StdGen))
The part in parentheses is basically State R.StdGen a
. In fact you can rewrite it quite easily to have the signature
random_response :: a -> [a] -> State R.StdGen a
I'll leave that as an exercise, but will address giveRandomElement
instead, which currently is wrong. Note that generator
is just a global constant, i.e. giveRandomElement
will always yield the same element of the list. That's no good.
Instead, this should also have a proper random type, preferrably again the state monad. You can write it thus:
giveRandomElement :: [a] -> State R.StdGen a
giveRandomElement lst = do
let n = length lst
rand <- state $ randomR (0, n-1)
return $ lst !! rand