Is it possible to nest guards in Haskell?

前端 未结 8 908
野的像风
野的像风 2020-12-10 16:52

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

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

pars         


        
相关标签:
8条回答
  • 2020-12-10 17:02

    It is possible to chain guards, with ,. This does basically the same as && in fjarri's answer, but is more versatile when it comes to pattern guards.

    What's not possible is nesting guards. Well, in your example that's only actually needed in the first clause. You could write

    parseNumber (h:ls)
        | isDigit h
           = if isNothing p
               then Just ([h], ls)        -- Digit found    <<< ERROR!!
               else Just (h:fst d, snd d) -- Ends in a digit
        | h == '.'
        , not ('.' `elem` snd d)
           = Just (h:fst d, snd d)    -- We don't want multiple dots
        | otherwise = Nothing       -- Not a number, stop looking!
    
    0 讨论(0)
  • 2020-12-10 17:03

    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
    
    0 讨论(0)
  • 2020-12-10 17:04

    Put them in separated functions.

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

    Have to admit I did not tested this code.

    0 讨论(0)
  • 2020-12-10 17:05

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

    0 讨论(0)
  • 2020-12-10 17:09

    When your function becomes exceedingly complicated and you cannot support the logic which is implemented with just guards alone, consider writing the function with abstract control functions instead:

    import Control.Applicative 
    import Control.Monad 
    
    isDigit :: Char -> Bool
    isDigit c = c >= '0' && c <= '9'
    
    parseNumber :: String -> Maybe (String, String)
    parseNumber [] = return ("", "")
    parseNumber (h:ls) = dig <|> dot where -- h is either a digit or a dot
      p = parseNumber ls 
      dig = do 
        guard (isDigit h)                                 -- ensure h is a digit
        fmap (\(ds,r) -> (h:ds,r)) p 
          <|> return ([h],ls)  -- the alternative between two computations
                               -- either the tail is parsed and h prepended to the result
                               -- or the digit is returned by itself                         
      dot = do 
        guard (h == '.')             -- ensure h is a dot
        (ds,r) <- p                  -- parse the tail
        guard $ not $ '.' `elem` ds  -- ensure there is no dot in the tail
        return (h:ds,r)              -- result
    

    This uses the Monad, Functor, and MonadPlus instances of Maybe to implement the parsing logic. In fact, this function generalizes to the type MonadPlus m => String -> m (String, String) - there is no actual use of Maybe constructors here.

    The function is also easy to read. It is much more evident what is happening that in the version with guards.

    0 讨论(0)
  • 2020-12-10 17:12

    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).

    0 讨论(0)
提交回复
热议问题