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?
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:
This uses the
Monad
,Functor
, andMonadPlus
instances ofMaybe
to implement the parsing logic. In fact, this function generalizes to the typeMonadPlus m => String -> m (String, String)
- there is no actual use ofMaybe
constructors here.The function is also easy to read. It is much more evident what is happening that in the version with guards.
Using
where Just d = ...
is dangerous: if you ever access it whenp
isNothing
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 themaybe
eliminator, or using functor/applicative/monad tools. Let's usecase
to keep it simple:We can also directly pattern match on the subcomponents of
d
:No, you can't. We all want it, but nobody can come up with a sensible syntax.
No, but you can use cases if you'd like:
Alternatively, multiway if works in a similar manner (
if True | p1 -> b ; | p2 -> c
).Put them in separated functions.
Have to admit I did not tested this code.
Recent GHC now has
MultiWayIf
:But this is better written slightly differently anyhow, without the partiality.
and you may as well use
maybe
.