Haskell newbie here, trying to write code to parse math expressions. Code:
isDigit :: Char -> Bool
isDigit c = c >= \'0\' && c <= \'9\'
pars
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!
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
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.
No, you can't. We all want it, but nobody can come up with a sensible syntax.
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.
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
).