是否有可能在Haskell中筑巢守卫?

时间:2015-12-07 00:17:04

标签: haskell

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中不允许使用链式防护装置吗?或者我是以某种方式错误地写这个?另外,我还有什么方法可以用简单的方式链接逻辑?

8 个答案:

答案 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}},FunctorMonadPlus个实例来实现解析逻辑。实际上,这个函数推广到类型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 = ...是危险的:如果您在pNothing时访问它,则整个程序都会崩溃。通过这样做,你必须在你的代码中添加这样的检查(正如你已经做过的那样),并注意不要忘记其中任何一个。

有更安全的方法,例如使用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

不得不承认我没有测试过这段代码。