Search code examples
haskelltypesfunctional-programmingmonadsstate-monad

Unpacking and printing values from State-Monad in Haskell using get


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


Solution

  • 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)