I am trying to get a good grasp of the State-Monad (and Monads in general) but I am struggling with rewriting the below function using the state Monad and the do-notation, which resulted as an exercise for me propose here
import Control.Monad
import System.Random
import Data.Complex
import qualified System.Random as R
import Control.Monad.Trans.State.Lazy
giveRandomElement :: [a] -> State R.StdGen a
giveRandomElement lst = do
let n = length lst
rand <- state $ randomR (0, n-1)
return $ lst !! rand
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
tal <- state $ randomR (0, 1) :: StateT StdGen Data.Functor.Identity.Identity a
if (tal == 0) then true_answer
else giveRandomElement answers
As is immediately obvious there are some type problems for the tal
-variable as it occurs in the if
-clause and the first line of the do
-expression. As is visible from the code I have tried to force the latter by a specific type in order to make it unambiguous and clearer for myself as well. I have done so by the compiler-suggestion I got when I first tried to force it to be of the Int
-type. I Am however not able to use that value in an if-statement, and I am unsure of how to convert or unpack the value such that I get it as an Int
.
So far I have tried to add the folloowing line after tal <- ...
, resp <- get $ tal
but I get this output.
error:
* Couldn't match expected type: t0
-> StateT StdGen Data.Functor.Identity.Identity a1
with actual type: StateT s0 m0 s0
* The first argument of ($) takes one value argument,
but its type `StateT s0 m0 s0' has none
In a stmt of a 'do' block: resp <- get $ tal
In the expression:
do tal <- state $ randomR (0, 1)
resp <- get $ tal
if (resp == 0) then
giveRandomElement answers
else
giveRandomElement answers
* Relevant bindings include tal :: t0
Furthermore I am baffled what would be the best way to 'print' the result returned by giveRandomElement
as the type is based on the type declared for the State
-monad which as I understand it doesn't use the deriving Show
also. But this can perhaps be solved by unpacking the value as enquired about above.
EDIT
I used the above packages although they are probably not all used in the above code. I am unsure of which is used by the code by I suspect the qualified System.Random as R
CodePudding user response:
The following code line:
tal <- state $ randomR (0, 1) :: StateT StdGen Data.Functor.Identity.Identity a
is quite long and might cause a horizontal slider to appear, at least on my platform.
So it is all too easy to overlook that at its very end, the a
type variable is used, while it should be just Int
.
Also, the two branches of the if
construct use different types, making the construct ill-typed. The then
branch gives a pure a
value, while the else
branch gives a monadic value. This is easily fixed by changing to:
if (tal == 0) then return true_answer
as the (slightly misnamed) return
library function wraps its argument into the monad at hand.
The following code, which tries to keep code lines short enough, seems to work fine:
import Control.Monad.State
import qualified System.Random as R
import qualified Data.Functor.Identity as DFI
giveRandomElement :: [a] -> State R.StdGen a
giveRandomElement lst = do
let n = length lst
rand <- state $ R.randomR (0, n-1)
return $ lst !! rand
type ActionType = StateT R.StdGen DFI.Identity Int
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
tal <- (state $ R.randomR (0, 1) :: ActionType)
if (tal == 0) then return true_answer
else giveRandomElement answers
main :: IO ()
main = do
let g0 = R.mkStdGen 4243
action = random_response_monad 20 [0..9]
(k, g1) = runState action g0
putStrLn $ "k is set to: " (show k)
Side note: the code can also be made to compile without the complex type annotation, like this:
tal <- state $ R.randomR (0::Int, 1)
CodePudding user response:
Something like this seems to work:
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
tal <- state $ randomR (0 :: Int, 1)
if (tal == 0) then return true_answer
else giveRandomElement answers
Two changes:
- Use a type annotation to tell the compiler what you mean by
0
and1
. Once you've told the compiler which type0
is, it follows that1
has the same type. (Keep in mind that in Haskell, numbers are polymorphic. Without more information, Haskell will see a literal such as0
as potentially anyNum
instance.) return
in front oftrue_answer
.
Here's a few samples from GHCi that seems to indicate that it works:
ghci> evalState (random_response_monad 42 [0..9]) <$> newStdGen
4
ghci> evalState (random_response_monad 42 [0..9]) <$> newStdGen
1
ghci> evalState (random_response_monad 42 [0..9]) <$> newStdGen
42