Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to nest guards in Haskell?

Tags:

haskell

Haskell newbie here, trying to write code to parse math expressions. Code:

isDigit :: Char -> Bool
isDigit c = c >= '0' && c <= '9'

parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
    | isDigit h
        | p == Nothing = Just([h], ls)      -- Digit found    <<< ERROR!!
        | otherwise = Just (h:fst d, snd d) -- Ends in a digit
    | h == '.'
        | p == Nothing = Nothing                                -- Ends in a point
        | not ('.' `elem` (snd d)) = Just (h:(fst d), snd d)    -- We don't want multiple dots
    | otherwise = Nothing       -- Not a number, stop looking!
    where 
        p = parseNumber ls
        Just d = parseNumber ls -- Float version of p. Not used if p is Nothing  

This function is supposed to take a string that starts with a number, and returns the number separated from the rest of the expression. Example:

parseNumber "123.0 + 2"

("123.0", " + 2")

I think this nested guards' syntax reads really nicely, but it doesn't work. The error reads, for the marked line:

parse error on input `|'

Are chained guards not allowed in Haskell? Or am I writting this wrongly somehow? Also, what alternatives do I have to chain logic in a simple way?

like image 431
SlySherZ Avatar asked Dec 07 '15 00:12

SlySherZ


People also ask

Can you pattern match in guards Haskell?

The PatternGuards extension, now officially incorporated into the Haskell 2010 language, expands guards to allow arbitrary pattern matching and condition chaining. The existing syntax for guards then becomes a special case of the new, much more general form. You start a guard in the same way as always, with a | .

How do guards work in Haskell?

A guard is basically a boolean expression. If it evaluates to True, then the corresponding function body is used. If it evaluates to False, checking drops through to the next guard and so on. If we call this function with 24.3, it will first check if that's smaller than or equal to 18.5.

When should I use guards Haskell?

Overview. Haskell guards are used to test the properties of an expression; it might look like an if-else statement from a beginner's view, but they function very differently. Haskell guards can be simpler and easier to read than pattern matching .

What does otherwise do in Haskell?

In the prelude, it defines otherwise = True . Using it in a pattern match just shadows that definition, introducing a new, more local variable which also happens to be called otherwise .


4 Answers

Recent GHC now has MultiWayIf:

{-# LANGUAGE MultiWayIf #-}

parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
  | isDigit h = if
    | p == Nothing -> Just ([h], ls)
    | otherwise    -> Just (h:fst d, snd d)
  | h == '.'  = if
    | p == Nothing             -> Nothing
    | not ('.' `elem` (snd d)) -> Just (h:(fst d), snd d)
  | otherwise = Nothing
  where p@(~(Just d)) = parseNumber ls

But this is better written slightly differently anyhow, without the partiality.

{-# LANGUAGE MultiWayIf #-}

parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
  | isDigit h = if
    | Nothing <- p -> Just ([h], ls) -- PatternGuards, on by default
    | Just d  <- p -> Just (h:fst d, snd d)
  | h == '.'  = if
    | Nothing <- p                         -> Nothing
    | Just d  <- p, not ('.' `elem` snd d) -> Just (h:(fst d), snd d)
  | otherwise = Nothing
  where p = parseNumber ls

and you may as well use maybe.

parseNumber :: String -> Maybe (String, String)
parseNumber "" = Just ("", "")
parseNumber (h:hs)
  | isDigit h = maybe (Just ([h], hs)) (\(num, rest') -> Just (h:num, rest')) rest
  | h == '.'  = maybe Nothing (\(num, rest') -> if '.' `elem` num then Nothing
                                                else Just (h:num, rest')
                              ) rest -- This logic is a bit wonky; it doesn't really work
  | otherwise = Nothing
  where rest = parseNumber hs
like image 63
HTNW Avatar answered Nov 26 '22 05:11

HTNW


No, but you can use cases if you'd like:

parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
    | isDigit h =
         case () of
           () | p == Nothing -> Just([h], ls)
              | otherwise -> Just (h:fst d, snd d) -- Ends in a digit
    | h == '.' =
         case () of
           () | p == Nothing -> Nothing
              | not ('.' `elem` (snd d)) -> Just (h:(fst d), snd d)
    | otherwise = Nothing
    where 
        p      = parseNumber ls
        Just d = parseNumber ls

Alternatively, multiway if works in a similar manner (if True | p1 -> b ; | p2 -> c).

like image 20
Thomas M. DuBuisson Avatar answered Nov 26 '22 06:11

Thomas M. DuBuisson


No, you can't. We all want it, but nobody can come up with a sensible syntax.

like image 34
augustss Avatar answered Nov 26 '22 05:11

augustss


No, it's not possible. Why not just write it linearly as

isDigit :: Char -> Bool
isDigit c = c >= '0' && c <= '9'

parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
    -- Digit found
    | isDigit h && p == Nothing = Just([h], ls)
    -- Ends in a digit
    | isDigit h = Just (h:fst d, snd d)
    -- Ends in a point
    | h == '.' && p == Nothing = Nothing
    -- We don't want multiple dots
    | h == '.' && not ('.' `elem` (snd d)) = Just (h:(fst d), snd d)
    -- Not a number, stop looking!
    | otherwise = Nothing
    where
        p = parseNumber ls
        Just d = parseNumber ls -- Float version of p. Not used if p is Nothing

main = print $ parseNumber "123.0 + 2"

If your guards become too involved it's probably a sign that you need to extract a function.

like image 36
fjarri Avatar answered Nov 26 '22 06:11

fjarri