Search code examples
haskellconditional-statements

How do I write nested if/then statements in haskell?


I'm trying to learn haskell by writing a chess game, and gchi is complaining about one of the functions I'm trying to write (error message: "parse error on input ‘where’ "), which is supposed to check whether a candidate chess move is legal. I suspect there's something about the syntax of the if/then expressions that I'm screwing up. The function at this point of development is written as follows:

isLegal :: Int -> Int -> Int -> Int -> Board -> Bool
isLegal from_x from_y to_x to_y (MakeBoard board_state) =
        if (piece_at_origin == Nothing)
                then False
        
        else if (piece_at_origin == Just (Piece Pawn White))
                then 
                        if not (to_y == from_y + 1)
                                then False
                        else if (x_dist /= 1 && x_dist /= 0)
                                then False
                        else if (x_dist /= 0 && piece_at_destination == Nothing)
                                then False
 
                                        
        else
                True

        where
                piece_at_origin = board_state from_x from_y
                piece_at_destination = board_state to_x to_y 
                x_dist = abs (to_x - from_x)
                y_dist = abs (to_y - from_y)

When I delete the "else if" part, I don't get any problems from ghci. I'm very new to haskell so I'm sure I'm making some basic mistake here, but I would be grateful if someone could point me to what I'm doing wrong. Thanks.


Solution

  • Haskell has no statements, it has only expressions. This means that an if … always has a then and else, and the two subexpression (the one of the then and the else need to have the same type). This is compiled in some expression that will return the then part in case the condition is True, and the else part otherwise.

    But what you aim to do can probably be expressed simpler with guards:

    isLegal :: Int -> Int -> Int -> Int -> Board -> Bool
    isLegal from_x from_y to_x to_y (MakeBoard board_state)
      | Nothing <- piece_at_origin = False
      | Just (Piece Pawn White) <- piece_at_origin =
          if not (to_y == from_y + 1)
            then False
            else
              if (x_dist /= 1 && x_dist /= 0)
                then False
                else
                  if (x_dist /= 0 && piece_at_destination == Nothing)
                    then False
                    else True
      | otherwise = True
      where
        piece_at_origin = board_state from_x from_y
        piece_at_destination = board_state to_x to_y
        x_dist = abs (to_x - from_x)
        y_dist = abs (to_y - from_y)

    In fact it might be better to work with a helper function that decides for each piece what to do:

    isLegal :: Int -> Int -> Int -> Int -> Board -> Bool
    isLegal from_x from_y to_x to_y (MakeBoard board_state)
      | Just p <- piece_at_origin = go p
      | otherwise = False
      where
        piece_at_origin = board_state from_x from_y
        piece_at_destination = board_state to_x to_y
        x_dist = abs (to_x - from_x)
        y_dist = abs (to_y - from_y)
        go (Piece Pawn White)
          | to_y /= from_y + 1 = False
          | x_dist /= 1 && x_dist /= 0 = False
          | x_dist /= 0, Nothing <- piece_at_destination = False
          | otherwise = True
        -- …