Home > front end >  Unpacking and printing values from State-Monad in Haskell using get
Unpacking and printing values from State-Monad in Haskell using get

Time:12-01

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 and 1. Once you've told the compiler which type 0 is, it follows that 1 has the same type. (Keep in mind that in Haskell, numbers are polymorphic. Without more information, Haskell will see a literal such as 0 as potentially any Num instance.)
  • return in front of true_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
  • Related