1

Hi i have a very noob question, lets say i want to create a game when you have to answer questions, i wrote this

data Question = Question { answer::String, text::String }
data Player = Player { name::String, points::String }

answerQuestion ::  Question -> Player -> Player 
answerQuestion question player
    | isCorrect question playerAnswer = Player (name player) (points player + 1)
    | otherwise = player
    where
      playerAnswer = do
          putStrLn text(question)
          getLine

isCorrect ::  Question -> String -> Bool 
isCorrect question try = try == answer(question)

now playerAnswer has type IO String so do i have to call isCorrect inside the do block ? Is there another way to parse IO String into String ?

In case of first i am feeling like loosing all the benefits from functional programming because i will end up writing my entire code in do blocks in order to access the String value

epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74
Alberto Pellizzon
  • 589
  • 1
  • 7
  • 23
  • your trouble start when you realize that `answerQuestion` will not compile (as `playerAnswer` is an `IO String` action) - that's why you should split up the functionality and add the answer as an additional `String` argument to your `answerQuestion` IMO – Random Dev Jan 13 '16 at 17:15
  • Nice approach, so how can i perform a string match for an `IO String` answer against a `answer(question)` `String` – Alberto Pellizzon Jan 13 '16 at 17:51
  • btw. `points :: String` - is this really what you want? – epsilonhalbe Jan 13 '16 at 18:07
  • 2
    I recommend that you read ["Inside My World (Ode to Functor and Monad)"](http://blog.jle.im/entry/inside-my-world-ode-to-functor-and-monad). It addresses this topic very well, I think. – Luis Casillas Jan 13 '16 at 20:41
  • Related: https://stackoverflow.com/q/7154518 . – atravers Sep 27 '21 at 04:00

3 Answers3

6

Note: This post is written in literate Haskell. You can save it as Game.lhs and try it in your GHCi.

now playerAnswer has type IO String so do i have to call isCorrect inside the do block?

Yes, if you stay on that course.

Is there another way to parse IO String into String?

None that's not unsafe. An IO String is something that gives you a String, but whatever uses the String has to stay in IO.

In case of first i am feeling like loosing all the benefits from functional programming because i will end up writing my entire code in do blocks in order to access the String value .

This can happen if you don't take measurements early. However, let's approach this from a top-down approach. First, let's introduce some type aliases so that it's clear whether we look at a String in as an answer or a name:

> type Text   = String
> type Answer = String
> type Name   = String
> type Points = Int    -- points are usually integers

Your original types stay the same:

> data Question = Question { answer :: Answer
>                          , text   :: Text } deriving Show
> data Player   = Player { name   :: Name
>                        , points :: Points } deriving Show

Now let's think about a single turn of the game. You want to ask the player a question, get his answer, and if he's right, add some points:

> gameTurn :: Question -> Player -> IO Player
> gameTurn q p = do
>    askQuestion q
>    a <- getAnswer
>    increasePointsIfCorrect q p a

This will be enough to fill your game with a single turn. Let's fill those functions with life. askQuestions and getAnswer change the world: they print something on the terminal and ask for user input. They have to be in IO at some point:

> askQuestion :: Question -> IO ()
> askQuestion q = putStrLn (text q)

> getAnswer :: IO String
> getAnswer = getLine

Before we actually define increasePointsIfCorrect, let's think about a version that does not use IO, again, in a slightly more abstract way:

> increasePointsIfCorrect' :: Question -> Player -> Answer -> Player
> increasePointsIfCorrect' q p a =
>    if isCorrect q a
>       then increasePoints p
>       else p

By the way, if you watch closely, you'll notice that increasePointsIfCorrect' is actually a single game turn. After all, it's checks the answer and increases the points. Speaking of:

> increasePoints :: Player -> Player
> increasePoints (Player n p) = Player n (p + 1)

> isCorrect :: Question -> Answer -> Bool
> isCorrect q a = answer q == a

We now defined several functions that don't use IO. All that's missing is increasePointsIfCorrect:

> increasePointsIfCorrect :: Question -> Player -> Answer -> IO Player
> increasePointsIfCorrect q p a = return (increasePointsIfCorrect' q p a)

You can check this now with a simple short game:

> theQuestion = Question { text   = "What is your favourite programming language?"
>                        , answer = "Haskell (soon)"}
> thePlayer   = Player { name   = "Alberto Pellizzon"
>                      , points = 306 }
>
> main :: IO ()
> main = gameTurn theQuestion thePlayer >>= print

There are other ways to handle this, but I guess this is one of the easier ones for beginners.

Either way, what's nice is that we could now test all the logic without using IO. For example:

prop_increasesPointsOnCorrectAnswer q p =
   increasePointsIfCorrect' q p (answer q) === increasePoints p

prop_doesnChangePointsOnWrongAnswer q p a = a /= answer q ==>
   increasePointsIfCorrect' q p a === p

ghci> quickCheck prop_increasesPointsOnCorrectAnswer 
OK. Passed 100 tests.
ghci> quickCheck prop_doesnChangePointsOnWrongAnswer 
OK. Passed 100 tests.

Implementing those tests completely is out of scope of this question though.

Exercises

  • Tell the player whether his answer was correct.
  • Add playGame :: [Question] -> Player -> IO (), which asks several questions after another and tells the player the final score.
  • Ask the player for his/her name and store it in the initial player.
  • (Very Hard for a beginner) Try to find a way so that you can either play a game automatically (for example for testing), or "against" a human. Hint: Look for "domain specific language".
Zeta
  • 103,620
  • 13
  • 194
  • 236
  • One of these days I'm going to write a normal sized answer. – Zeta Jan 13 '16 at 18:13
  • 4
    or just write some script to extract all your answers, string them together and publish "Haskell the ultimate reference to programming the universe and all the rest" ;) – Random Dev Jan 13 '16 at 18:14
  • 1
    @Carsten Sure. "In the beginning, my account was created. This has made a lot of people very angry and been widely regarded as a bad move.". – Zeta Jan 13 '16 at 21:20
  • Hi guys thank you all for your answers, it will took me some time to read and understand everything, one thing that i noticed is that all 3 answers are considerably different and maybe this means that it not an easy topic :D, i will accept this as the right answer(of course because it is the longest) when i understand it completely – Alberto Pellizzon Jan 14 '16 at 16:09
1

as an alternative you can promote answerQuestion to an action as well:

answerQuestion ::  Question -> Player -> IO Player 
answerQuestion question player =
    answer <- playerAnswer
    if isCorrect question answer
    then return $ Player (name player) (points player + 1)
    else return player
    where
      playerAnswer = do
          putStrLn $ text question
          getLine

so yes you could say that in this case you should call isCorrect from inside a do block

Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • This is a viable solution what i don't like about that is 1 - writing the entire function in an imperative way (if / then / else) 2 - i want `askQuestion` to return a Player 3 - if i have to call `answerQuestion` from another function i also have to call it inside a do block and so on – Alberto Pellizzon Jan 13 '16 at 17:47
  • well you can rewrite it to `case` if you like but it's just the same as yours ... and once you enter `do` it will always look very imperative - btw: what is your question then? – Random Dev Jan 13 '16 at 17:48
  • So the real problem here is that i am not able to perform a match with an IO String against a String and in order to do that from what i know i have to "covert" the IO String into a String and this can be done only inside a do block but doing that will make my code to be only inside that and to me it seems an hack, i am talking from a very beginner prospective and you actually already answer my question so i am going to accept it, just trying to dig deeper in this problem – Alberto Pellizzon Jan 13 '16 at 17:59
  • 1
    well not exactly - you don't convert some values - you should think of `IO String` as an *action* that you can *execute* (more than once if you like) and get back a `String` (if you execute it more than once than those may be different) - you can join/bind/compose together those *actions* (that's the monad part) and one way of doing this is the `do`-block syntactic sugar - but in the end you will only execute those actions when the runtimes execute the `main :: IO ()` *action* – Random Dev Jan 13 '16 at 18:10
  • @chi yes thanks - in this case I only copy&pasted the code – Random Dev Jan 13 '16 at 18:11
0

Is there another way to parse IO String into String?

No, there is no safe way of turning IO String into String. This is the point of the IO type!

now playerAnswer has type IO String so do i have to call isCorrect inside the do block ? [...] In case of first i am feeling like loosing all the benefits from functional programming because i will end up writing my entire code in do blocks in order to access the String value

That's what it looks like at first, but it's not like that. The trick is that we use adapter functions to connect the pure and the effectful worlds. So for example, suppose you have:

  • A question question :: IO Question
  • An answer answer :: IO String

And you want to call isCorrect :: Question -> String -> Bool on the Question and the String. One way to do this is to use the liftA2 function:

import Control.Applicative (liftA2)

example :: IO Bool
example = liftA2 isCorrect question answer
  where question :: IO Question
        question = _
        answer :: IO String
        answer = _

liftA2 has this generic type:

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

In this example, we're using it with:

  • f := IO
  • a := Question
  • b := String
  • c := Bool

So what liftA2 does is adapt a pure function to work with side-effecting types. And that's how Haskell programming generally works:

  1. You try to write most of your code as pure functions
  2. You use auxiliary functions like liftA2 to adapt them to work with impure effects.

It takes some study and practice to get the hang of this.

Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
  • Sorry, but that's a bad example. The `Question` is already known at that point. `liftM (isCorrect question) getAnswer` or `fmap (isCorrect question) getAnswer` would be better, unless your `question :: IO Question` is the slightly bogus `putQuestion q >> return q`. Also, you're using the already existing function `answer` in a local binding, shadowing the `answer :: Question -> String` OP provided in his question. The overall mentality/paradigm of your answer holds, but that example is probably confusing for a language beginner. – Zeta Jan 13 '16 at 21:17