Reputation: 1661
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?
Upvotes: 14
Views: 18933
Reputation: 64740
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
).
Upvotes: 21
Reputation: 29193
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
Upvotes: 10
Reputation: 3504
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.
Upvotes: 0
Reputation: 23014
No, you can't. We all want it, but nobody can come up with a sensible syntax.
Upvotes: 16
Reputation: 116139
Using where Just d = ...
is dangerous: if you ever access it when p
is Nothing
your whole program will crash. By doing that, you have to add such checks in your code (as you correctly already did), and be careful not to forget any one of these.
There are safer ways, such as using case p of Nothing -> ... ; Just d -> ...
, using the maybe
eliminator, or using functor/applicative/monad tools. Let's use case
to keep it simple:
parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
| isDigit h = case p of
Nothing -> Just([h], ls) -- Digit found <<< ERROR!!
Just d -> Just (h:fst d, snd d) -- Ends in a digit
| h == '.' = case p of
Nothing -> Nothing -- Ends in a point
Just d | not ('.' `elem` (snd d))
-> Just (h:(fst d), snd d) -- We don't want multiple dots
_ -> Nothing -- Not a number, stop looking!
where
p = parseNumber ls
We can also directly pattern match on the subcomponents of d
:
parseNumber :: String -> Maybe (String, String)
parseNumber [] = Just ("", "")
parseNumber (h:ls)
| isDigit h = case p of
Nothing -> Just([h], ls) -- Digit found <<< ERROR!!
Just (hs,rest) -> Just (h:hs, rest) -- Ends in a digit
| h == '.' = case p of
Nothing -> Nothing -- Ends in a point
Just (hs, rest) | not ('.' `elem` rest)
-> Just (h:hs, rest) -- We don't want multiple dots
_ -> Nothing -- Not a number, stop looking!
where
p = parseNumber ls
Upvotes: 1
Reputation: 120711
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!
Upvotes: 3
Reputation: 14578
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.
Upvotes: 5
Reputation: 9726
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.
Upvotes: 3