Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guess My Number, a monadic headache

To test my skills in Haskell, I decided I wanted to implement the very first game you find in Land of Lisp / Realm of Racket. The "Guess My Number" game. The game relies on mutable state to run, as it constantly has to update upper and lower bounds for the program to home in on the value the user is thinking of.

It goes a little something like this:

> (guess)
50
> (smaller)
25
> (bigger)
37

Now, this sort of thing (to my knowledge) isn't entirely possible in Haskell, calling some function from the REPL that modifies global mutable state, then prints the result immediately after, as it violates the principle of immutability. Therefore all of the interaction must live inside of an IO and/or State monad. And that's where I'm stuck.

I can't seem to be able to wrap my mind around combining the IO monad and the State monad, so I can get input, print results and modify state, all in the same function.

Here's what I got so far:

type Bound = (Int, Int) -- left is the lower bound, right is the upper

initial :: Bound
initial = (1, 100)

guess :: Bound -> Int
guess (l, u) = (l + u) `div` 2

smaller :: State Bound ()
smaller = do
  bd@(l, _) <- get
  let newUpper = max l $ pred $ guess bd
  put $ (l, newUpper)

bigger :: State Bound ()
bigger = do
  bd@(_, u) <- get
  let newLower = min u $ succ $ guess bd
  put $ (newLower, u)

All I need to do now, is to devise a way to

  • print initial guess
  • receive command wanting smaller/bigger number
  • modify state accordingly
  • call the function recursively so it guesses again

How do I combine IO and State in an elegant way to achieve this?

Note: I'm aware that this can probably be achieved without the use of state at all; but I want to make it stay true to the original

like image 397
Electric Coffee Avatar asked Jan 27 '15 19:01

Electric Coffee


Video Answer


3 Answers

You can combine different monads using monad transformers - in this case StateT. You can use your existing code by changing the type signatures to use StateT:

bigger, smaller :: Monad m => StateT Bound m ()

then you can write a function to run the game given a state parameter:

game :: StateT Bound IO ()
game = do
  s <- get
  liftIO $ print (guess s)
  verdict <- (liftIO getLine)
  case verdict of
    "smaller" -> smaller >> game
    "bigger" -> bigger >> game
    "ok" -> return ()
    _ -> (liftIO $ putStrLn $ "Unknown verdict " ++ verdict) >> game

you use liftIO to lift an IO action into the StateT Bound IO monad, allowing you to prompt for input and read the next line.

Finally you can run the game using runStateT:

runStateT game initial
like image 137
Lee Avatar answered Sep 29 '22 05:09

Lee


What you ask is sort of possible...

import Data.IORef

makeGame :: IO (IO (), IO (), IO ())
makeGame = do
    bound <- newIORef (1, 100)
    let guess = do
            (min, max) <- readIORef bound
            print $ (min + max) `div` 2

        smaller = do
            (min, max) <- readIORef bound
            let mid = (min + max) `div` 2
            writeIORef bound (min, mid)
            guess

        bigger = do
            (min, max) <- readIORef bound
            let mid = (min + max) `div` 2
            writeIORef bound (mid, max)
            guess

    return (guess, smaller, bigger)

Nevermind how much redundancy is in that code, this was just a quick proof of concept. Here's an example session:

$ ghci guess.hs 
GHCi, version 7.9.20141202: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( guess.hs, interpreted )
Ok, modules loaded: Main.
*Main> (guess, smaller, bigger) <- makeGame 
*Main> guess
50
*Main> smaller
25
*Main> bigger
37
*Main> 

Nested IO types can be fun and useful.

like image 36
Carl Avatar answered Sep 29 '22 03:09

Carl


You don't have to use the State monad at all in this example. Here is an example that passes the state as parameter:

loop :: Bound -> IO ()
loop bd@(l,u) = do
  putStr "> "
  line <- getLine
  case line of
   "(guess)" -> print (guess bd) >> loop bd
   "(smaller)" -> do
     let newUpper = max l $ dec $ guess bd
     print $ guess (l, newUpper)
     loop (l, newUpper)
   "(bigger)" -> do
     let newLower = min u $ inc $ guess bd
     print $ guess (newLower, u)
     loop (newLower, u)
   "" -> return ()
   _ -> putStrLn "Can't parse input" >> loop bd

main :: IO ()
main = loop initial

Otherwise, the concept you are looking for are monad transformers. For example using StateT:

smaller :: StateT Bound IO ()
smaller = do
  bd@(l, _) <- get
  let newUpper = max l $ dec $ guess bd
  put $ (l, newUpper)

bigger :: StateT Bound IO ()
bigger = do
  bd@(_, u) <- get
  let newLower = min u $ inc $ guess bd
  put $ (newLower, u)

guessM :: StateT Bound IO ()
guessM = get >>= lift . print . guess

loop :: StateT Bound IO ()
loop = do
  lift $ putStr "> "
  line <- lift getLine
  case line of
   "(guess)" -> guessM >> loop
   "(smaller)" -> do
     smaller
     guessM
     loop
   "(bigger)" -> do
     bigger
     guessM
     loop
   "" -> return ()
   _ -> lift (putStrLn "Can't parse input") >> loop

main :: IO ()
main = evalStateT loop initial

See this chapter of Real World Haskell for a tutorial on the subject of monad transformers.

like image 22
hpdeifel Avatar answered Sep 29 '22 03:09

hpdeifel