1

I'm trying to write a function that will execute 2 different game modes which are defined as tictactoe :: IO () and main :: IO (). I'm getting a parse error on input '|'. I don't understand what I am doing wrong. Could someone please explain this to me?

tictac :: IO()
tictac = do 
       putStrLn "Would you like to play against the computer or 
                 another player? Enter: 2 Player or Computer"
       choice <- getLine
         |choice == "Computer" = main
         |choice == "2 Player" = tictactoe
         |otherwise = putStrLn "That's not a choice!"
hnefatl
  • 5,860
  • 2
  • 27
  • 49
wawaloo_17
  • 157
  • 3
  • 10

2 Answers2

5

There's a limited set of places where you can use guards - they're most commonly used only in function definitions. In this situation you're probably looking for the case statement instead:

choice <- getLine
case choice of
     "Computer" -> main
     "2 Player" -> tictactoe
      _         -> putStrLn "That's not a choice!"

The _ pattern matches anything, which "mops up" the leftover patterns. Its use here is analogous to otherwise, although otherwise is actually just syntactic sugar for true given that the expressions in guards are boolean.

It's not quite the same as guards, as guards evaluate boolean expressions while case does pattern matching, but it works. A more accurate dual of guards would be if expressions, but the case syntax is nicer when you can use it instead. Take a look at the control structures page on the wiki for more examples.


As pointed out in the comments, it's possible to use guards in case expressions as well - you can see this in the specification and in this question/answer. It does require at least one pattern match though, which is ugly here - you could use the "hack" described here to do something like:

case () of
 _ | choice == "Computer" -> main
   | choice == "2 Player" -> tictactoe
   | otherwise            -> putStrLn "That's not a choice!"

But here there's no advantage to doing so.

hnefatl
  • 5,860
  • 2
  • 27
  • 49
  • 1
    You can use guards in other places than top level functions. `case`, defining functions in `where` and `let`, even defining values that don't take parameters in `let`. – Potato44 Mar 28 '18 at 07:21
  • @Potato44 Thanks for pointing out that guards can be used in `case`, I'd not seen that syntax before. I've changed the opening to make it clear that I'm talking about all functions, rather than just top-level ones. I've not been able to find an example of your very last case though, would you mind pointing me towards an example? – hnefatl Mar 28 '18 at 08:55
  • The question asker of the link to the ["hack"](https://stackoverflow.com/questions/10370346/using-guards-in-let-in-expressions) you mentioned uses it in the `let` on the value `brainiac`. – Potato44 Mar 28 '18 at 09:41
  • 2
    The `case () of _ | choice == "Computer" -> … | choice == "2 Player" -> … | …` can be replaced with `if | choice == "Computer" -> … | choice == "2 Player" -> … | …` using the `MultiWayIf` extension, which has been available since GHC 7.6. – Jon Purdy Mar 28 '18 at 12:37
  • @JonPurdy I think that should be made a full answer because that is almost exactly what the question asker wrote besides the `if`. – Potato44 Mar 29 '18 at 12:52
  • @Potato44: Alright, I’ve added one. (Tried to make it different enough that it’s not redundant.) – Jon Purdy Mar 29 '18 at 16:00
  • 1
    What I don't like about guards is that they use an '=' sign after the boolean condition. It is not an equation, it is a result, an arrow. – fp_mora Mar 29 '18 at 17:33
  • The advantage of using guards in a case is having pattern matching drill down with booleans, and booleans again doing case. It can become as ugly as desired. – fp_mora Mar 29 '18 at 17:47
  • @fpmora: But it is an equation, when you’re writing a definition. It’d be inconsistent unless function definitions *without* guards used an arrow as well, like `f x y z -> impl`. A guard is just slotting `| condition` between `f x y z` and `=`, or `(Some pattern)` and `->` in `case`, without changing the surrounding syntax. – Jon Purdy Mar 29 '18 at 18:35
  • The right side of a guard expression is a result *because* a Boolean is True. In an equation the left side becomes the right side after beta reduction. Also, guards behave like '=' signs in recursive function definitions possible without an equal sign, such as f8 (a,b) | null b = [a] | True = a : (f8 (splitAt 3 b)) so, I guess, is consistent. TY! – fp_mora Mar 29 '18 at 20:06
1

There are a few ways to test values like this, but the way that’s most similar to what you’ve written is to use the extension called MultiWayIf, available since GHC 7.6 (September 2012). Add the pragma:

{-# LANGUAGE MultiWayIf #-}

To the top of your source file (or use :set -XMultiWayIf in GHCi) and you can write the following:

choice <- getLine
-- Simply add the ‘if’ keyword here.
if
  | choice == "Computer" -> main
  | choice == "2 Player" -> tictactoe
  | otherwise -> putStrLn "That's not a choice!"

Ordinarily, guard syntax | condition only works in two places: in definitions (where a guard goes after the name & arguments, before the = symbol) and in case (where it goes after the pattern, before the -> symbol):

doubleIfEven :: Int -> Int
-- Definition
doubleIfEven x
  | even x = x * 2
  --------
  | otherwise = x
  -----------

doubleIfJustEven :: Maybe Int -> Maybe Int
doubleIfJustEven mx
  -- Match
  = case mx of
    Just x
      | even x -> Just (x * 2)
      --------
      | otherwise -> Just x
      -----------
    Nothing -> Nothing

Here are the alternatives:

  1. case expressions, in this case where you’re only testing for (structural) equality on strings:

    case choice of
      "Computer" -> main
      "2 Player" -> tictactoe
      _ -> putStrLn "That's not a choice!"
    
  2. A local definition in a where clause or let binding:

    tictac :: IO ()
    tictac = do 
      putStrLn "Would you like to play against the computer or\
               \ another player? Enter: 2 Player or Computer"
      choice <- getLine
      check choice
    
      where
        check choice
          | choice == "Computer" = main
          | choice == "2 Player" = tictactoe
          | otherwise = putStrLn "That's not a choice!"
    
    ----
    
    tictac :: IO ()
    tictac = do 
      putStrLn "Would you like to play against the computer or\
               \ another player? Enter: 2 Player or Computer"
      let
        check choice
          | choice == "Computer" = main
          | choice == "2 Player" = tictactoe
          | otherwise = putStrLn "That's not a choice!"
      choice <- getLine
      check choice
    
  3. Nested if expressions:

    if choice == "Computer" then main
      else if choice == "2 Player" then tictactoe
      else putStrLn "That's not a choice!"
    
  4. case with a dummy pattern and guards (an old idiom):

    case () of
      _ | choice == "Computer" -> main
        | choice == "2 Player" -> tictactoe
        | otherwise -> putStrLn "That's not a choice!"
    

#1 is most common for matching; #2 is used if you need guards and either want to avoid MultiWayIf to support old compilers, or simply want to factor out the logic into a separate definition for readability; #3 and #4 aren’t very common or idiomatic, but there’s nothing “wrong” with them.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166