Guard inside 'do' block - haskell
Asked Answered
T

4

8

I want to write a simple game "guess number" - with n attempts. I want to add some conditions and hits. Is it possible to use guards inside do block ?

Here is my code:

game = return()
game n = do putStrLn "guess number: 0-99"
            number<-getLine
            let y = read number
            let x =20
            | y>x = putStrLn "your number is greater than x"
            | y<x = putStrLn "your number is less than x"
            | y==x  putStrLn "U win!!"
            | otherwise = game (n-1)

already got error

error: parse error on input ‘|’

Is it fixable with some white space, or just impossible to do?

Tantalic answered 16/1, 2020 at 22:2 Comment(0)
H
14

A do expression [Haskell-report] only consists out of exp, pat <- exp, and let … statements, and the compiler will desugar these. Hence without some language extensions, you can not write guards in a do block. Furthermore it is likely not a good idea to enable that anyway. What if you for example would want to use two "guard blocks" next to each other? Then the two would "merge" and thus the guards of the first block would already eleminate (nearly) all cases.

You can use another let clause here:

game :: IO ()
game 0 = return ()
game n = do
    putStrLn "guess number: 0-99"
    number <- getLine
    let y = read number
    let x = 20
    let action | y > x = putStrLn "your number is greater than x" >> game (n-1)
               | y < x = putStrLn "your number is less than x" >> game (n-1)
               | otherwise = putStrLn "U win!!"
    action

Note that the otherwise in the original question will never get triggered, since a value is less than, greater than, or equal to another value.

Hearst answered 16/1, 2020 at 22:14 Comment(1)
I have modified it a bit but game now is working. Thanks for hint.Tantalic
B
11

Lots of problems there.

First, you can't say game = something and game n = something, so remove the game = return () line. (You may have been trying to write a type signature, but that's not one.)

Second, you can't drop into guard syntax in arbitrary places. The closest valid thing to what you wrote are multi-way if-expressions, which would let you write this:

{-# LANGUAGE MultiWayIf #-}
game n = do putStrLn "guess number: 0-99"
            number<-getLine
            let y = read number
            let x =20
            if
              | y>x -> putStrLn "your number is greater than x"
              | y<x -> putStrLn "your number is less than x"
              | y==x-> putStrLn "U win!!"
              | otherwise -> game (n-1)

Third, the Ord typeclass is supposed to be for types with a total order, so unless you're using unlawful things like NaN, you'll always have one of y>x, y<x, or y==x, so the otherwise will never be entered.

Fourth, comparing with <, ==, and > is unidiomatic and slow, since it has to keep repeating the comparison. Instead of doing that, do something like this:

case y `compare` x of
  GT -> _
  LT -> _
  EQ -> _
Bassoon answered 16/1, 2020 at 22:14 Comment(4)
It helped me to solve it with 'case'. Thanks for hints.Tantalic
@Joseph, if case is more efficient wouldn't the compiler optimize the use of guards to produce the same machine code as case?Bertero
@GeorgeCo No, the compiler does not perform that optimization for you. The problem isn't guards vs. case either; it's < and > vs. compare.Bassoon
so, x < 0 is slower than compare x 0 == LT ?? or did I totally misunderstand what you wrote? EDIT: NVM ... I get it now!Bawdyhouse
K
4

You could also just use case or LambdaCase.

{-# LANGUAGE LambdaCase #-}

game  :: Int -> IO ()
game n = case n of
  0 -> putStrLn "used all attempts"
  n -> 
    putStrLn "guess a number: 0 - 99" >>
    (`compare` 20) . read <$> getLine >>= 
      \case 
        EQ -> putStrLn "U win"
        GT -> putStrLn "your number is greater than x" >> 
              game (n - 1)
        LT -> putStrLn "your number is less than x" >> 
              game (n - 1)
Kailakaile answered 18/1, 2020 at 1:27 Comment(0)
B
1

The other answers are very informative. Reading those led me to see that you can also call a function to solve this, e.g.

game = do putStrLn "guess number: 0-99"
          number <- getLine
          let y = read number
          let x = 20
          action y x
       where
           action y x 
            | y>x = putStrLn "your number is greater than x" >> game
            | y<x = putStrLn "your number is less than x" >> game
            | otherwise = putStrLn "U win!!"
Bertero answered 20/10, 2021 at 1:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.