Haskell新手在这里,试图编写代码来解析数学表达式。 代码:
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
此函数应该采用以数字开头的字符串,并返回与表达式其余部分分开的数字。例如:
parseNumber“123.0 + 2”
(“123.0”,“+ 2”)
我认为这个嵌套的守卫的语法读得非常好,但它不起作用。 错误读取标记的行:
输入“|”
时解析错误
Haskell中不允许使用链式防护装置吗?或者我是以某种方式错误地写这个?另外,我还有什么方法可以用简单的方式链接逻辑?
答案 0 :(得分:14)
不,但如果您愿意,可以使用案例:
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
或者,如果以类似方式工作(if True | p1 -> b ; | p2 -> c
),则多路。
答案 1 :(得分:12)
不,你不能。我们都想要它,但没有人能够提出合理的语法。
答案 2 :(得分:5)
当你的函数变得非常复杂并且你不能仅仅使用防护来支持逻辑时,考虑用抽象控制函数来编写函数:
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
这使用Monad
的{{1}},Functor
和MonadPlus
个实例来实现解析逻辑。实际上,这个函数推广到类型Maybe
- 这里没有实际使用MonadPlus m => String -> m (String, String)
构造函数。
该功能也易于阅读。在带有警卫的版本中发生的情况更为明显。
答案 3 :(得分:3)
不,这是不可能的。为什么不把它线性地写成
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"
如果您的警卫过于参与,则可能表明您需要提取功能。
答案 4 :(得分:3)
可以使用0
链式警卫。这与fjarri的答案中的,
基本相同,但在模式警卫方面则更为通用。
嵌套守卫是不可能的。那么,在你的例子中,只有第一个条款才真正需要。你可以写
&&
答案 5 :(得分:3)
最近的GHC现在有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
但无论如何,这种写得更好一些,没有偏见。
{-# 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
您也可以使用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
答案 6 :(得分:1)
使用where Just d = ...
是危险的:如果您在p
为Nothing
时访问它,则整个程序都会崩溃。通过这样做,你必须在你的代码中添加这样的检查(正如你已经做过的那样),并注意不要忘记其中任何一个。
有更安全的方法,例如使用case p of Nothing -> ... ; Just d -> ...
,使用maybe
消除器,或使用functor / applicative / monad工具。让我们使用case
来保持简单:
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
我们还可以直接对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
答案 7 :(得分:0)
将它们放在分开的函数中。
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
不得不承认我没有测试过这段代码。