bibble
bibble

Reputation: 59

Haskell: if-then-else conditioning problem

I am fairly new to Haskell and trying to comprehend writing functions and if else conditions and everything else. I am trying to write a very basic function but I don't fully understand if-then-else usage. I have a maze that i represent as [[Char]]. And this function will simply look at the position x,y in the maze and returns if it's a valid position or not. (whether it's in the maze boundaries or not)

I have written this so far:

is_valid_position :: Int -> Int -> [[Char]] -> Bool

is_valid_position x y maze
        if(x < 0 || y < 0)
            then False
        if(x >= length maze || y >= length (maze!!0))
            then False
        else True

This gives an error because of the 'else' usage right now. What I'm trying to write in python is like this:

def is_valid_position(maze, pos_r, pos_c):
    if pos_r < 0 or pos_c < 0:
        return False
    if pos_r >= len(maze) or pos_c >= len(maze[0]):
        return False
    
    return True

How should I change my Haskell code? I appreciate any help.

Upvotes: 3

Views: 652

Answers (2)

Mark Saving
Mark Saving

Reputation: 1787

Chepner's answer is excellent. But the "true Haskell" way of doing things would be a little bit different.

First, we define

(!!?) :: [a] -> Int -> Maybe a
[]     !!? _ = Nothing
(x:xs) !!? n = if n == 0
               then Just x
               else xs !!? n - 1

Then, we define

charAtPosition :: Int -> Int -> [[a]] -> Maybe a
charAtPosition x y maze = if x < 0 || y < 0
                          then Nothing else
                              case maze !!? x of
                                  Nothing -> Nothing
                                  Just column -> column !!? y

charAtPosition x y maze returns the character at position x, y if there is such a character. Otherwise, it returns 0. To check whether indices x and y are valid, we simply say

isValidPosition x y maze = isJust (charAtPosition x y maze)

(where isJust is imported from Data.Maybe).

Note that we can actually clean up charAtPosition using the power of monad notation. Haskell defines the following function:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
w >>= f = case w of
    Nothing -> Nothing
    Just k  -> f k

Using >>=, we can thus rewrite charAtPosition x y maze to be

charAtPosition x y maze = if x < 0 || y < 0
                          then Nothing
                          else (maze !!? x) >>= (!!? y)

Another common piece of monad notation is >>. In this case, >> is defined by

(>>) :: Maybe a -> Maybe b -> Maybe b
a >> b = case a of
    Nothing -> Nothing
    Just c  -> b

The last piece of the puzzle is the guard function, defined in Control.Monad. In this case, the definition is

guard :: Bool -> Maybe ()
guard bool = if bool
             then Just ()
             else Nothing

With the pieces of the puzzle, we can write

charAtPosition x y maze = guard (x >= 0 && y >= 0) >> (maze !!? x) >>= (!!? y)

We can make this slightly nicer using do notation to get

charAtPosition x y maze = do guard (x >= 0 && y >= 0)
                             (maze !!? x) >>= (!!? y)

which is the final Haskell way to write charAtPosition. Informally, we can read the definition of charAtPosition x y maze as saying: first, make sure that x >= 0 and that y >= 0. Then, attempt to look up the xth element of maze. Finally, attempt to look up the yth element of the result.

Upvotes: 2

chepner
chepner

Reputation: 530892

The if-else expression requires both parts. You can nest the expressions, so something like if c1 then a else if c2 then b else c.

is_valid_position x y maze = if (x < 0 || y > 0)
                             then False
                             else if (x >= length maze | y >= length (maze !! 0)
                                  then False
                                  else True

However, you don't need if-else expression here, since you are working purely with Boolean values. You can simply use && and ||.

-- Instead of prohibiting any condition to be true,
-- require each negated condition to be true
is_valid_position x y maze = x >= 0
                             && y >= 0
                             && x < length maze
                             && y < length (maze !! 0)

Guard conditions are also an option to break it up a little bit:

is_valid_position x y maze | x < 0 = False
                           | y < 0 = False
                           | x >= length maze = False
                           | y >= length (maze !! 0) = False
                           | otherwise = True

Upvotes: 4

Related Questions