如何在没有缩进树的情况下处理嵌套的case语句?

时间:2017-12-03 19:42:13

标签: haskell functional-programming

我偶尔遇到一个小问题,我有一组嵌套的case语句,处理起来很麻烦。我是否可以使用任何技术/模式来获得一个函数列表(这些函数等同于示例' case ...语句),评估所有函数,并选择匹配模式的第一个函数/模式(例如:Right x)?

我将它们放入列表中的具体问题是,它们不一定是同一类型(我认为是单态限制)。例如以下内容:

  let possibleTags = [
          parse (parseFileReference) "file reference" xStr
        , parse (parseGitDiffReference) "git diff tag" xStr
        ]

产生以下错误:

• Couldn't match type ‘GitDiffReference’ with ‘FileReference’
  Expected type: Either ParseError FileReference
    Actual type: Either ParseError GitDiffReference
• In the expression:
    parse (parseGitDiffReference) "git diff tag" xStr
  In the expression:
    [parse (parseFileReference) "file reference" xStr,
     parse (parseGitDiffReference) "git diff tag" xStr]
  In an equation for ‘possibleTags’:
      possibleTags
        = [parse (parseFileReference) "file reference" xStr,
           parse (parseGitDiffReference) "git diff tag" xStr]
abc :: Int -> String
abc = undefined

abc2 :: Int -> Float
abc2 = undefined

abc3 :: Int -> Int
abc3 = undefined

example :: Int -> Maybe String
example x = case abc x of 
  ("yes") -> Just "abc"
  ("no")  -> case abc2 x of
               1.0 -> Just "abc2"
               2.0 -> case abc3 x of
                        100 -> Just "abc3"
                        200 -> Nothing

我理想地寻找下面的内容(不是有效的代码):

-- Psuedo code
example :: Int -> Maybe String
example = 
 if (abc x && "yes") then Just "abc"
 if (abc2 x && 1.0) then Just "abc2"
 if (abc3 x && 100) then Just "abc3"

使用if条件的问题是我不能(据我所知)进行模式匹配,例如if (Just x)

3 个答案:

答案 0 :(得分:6)

翻译确切示例(保持非详尽模式匹配):

import Data.Foldable


example2 :: Int -> Maybe String
example2 x = asum tests
   where
   tests =
      [ case abc x of 
         "yes" -> Just "abc"
         "no"  -> Nothing
      , case abc2 x of
         1.0 -> Just "abc2"
         2.0 -> Nothing
      , case abc3 x of
         100 -> Just "abc3"
         200 -> Nothing
      ]

答案 1 :(得分:4)

至少在结果为Maybe时,这看起来像是Alternative实例的明确用例:

example x = ("abc" <$ guard (abc x=="yes"))
         <|>("abc2" <$ guard (abc2 x==1))
         <|>("abc3" <$ guard (abc3 x==100))

答案 2 :(得分:3)

我将使用你原来的例子。一个人为的例子就像你的第二个例子不太可能简化,而你的第一个看起来像monad变换器代码实际上是你可能在生产中看到的东西。

寻找一般&#34; if-then-case&#34;像你这样的陈述似乎不会有成果。这是因为案例陈述在Haskell中确实是全能的,并且减少将比case更具体,而不是更一般。因此,考虑到这一点,让我们来看看您的代码。

parseLine :: Text -> IO (Either String Text)
parseLine x = do
  let xStr = convertString x :: String
  case parse (parseFileReference) "file reference" xStr of
    Right z -> do
      print z
      fileReferenceContent z >>= \case
        Just v' -> return2x $ "```\n" <> v' <> "```"
        Nothing -> return $ Left "Unable to parse"
    Left _ -> case parse (parseGitDiffReference) "git diff tag" xStr of
        Right z -> do
          print z
          return2x $ ""
        Left _ -> case parse (parsePossibleTag) "possible tag" xStr of
          Right _ -> return $ Left $ "Tag that failed to match: " ++ xStr
          Left _ -> return2x x

在每种情况下,当您解析时,如果内容为Left,则忽略该内容。因此,让我们删除您忽略的无意义数据。

eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe (Left _) = Nothing
eitherToMaybe (Right x) = Just x

parse' :: Foo0 -> String -> String -> Maybe Bar -- where Foo0 and Bar are the correct types
parse' foo s0 s1 = eitherToMaybe $ parse foo s0 s1

然后拨打parse'而不是parse。这还没有改善任何事情。但是让我们重构一下,然后我们会看到它给我们带来了什么。我想要所有的成功&#34;案例(之前为Right zJust z代码中的parse')位于where块中。

parseLine x = do
  let xStr = convertString x :: String
  case parse' (parseFileReference) "file reference" xStr of
    Just z -> fileRef z
    Nothing -> case parse' (parseGitDiffReference) "git diff tag" xStr of
        Just z -> gitDiff z
        Nothing -> case parse' (parsePossibleTag) "possible tag" xStr of
          Just _ -> tagMatch xStr
          Nothing -> return2x x
    where fileRef z = do
                print z
                fileReferenceContent z >>= \case
                      Just v' -> return2x $ "```\n" <> v' <> "```"
                      Nothing -> return $ Left "Unable to parse"
          gitDiff z = do
                print z
                return2x ""
          tagMatch xStr = do
                return (Left $ "Tag that failed to match: " ++ xStr)

再一次,没有真正的好处。但事情就是这样:Maybe有一个Alternative实例可以完全按照您的要求进行操作,而Either却没有。具体来说,它将选择第一个成功的匹配,而忽略其他匹配。让我们拉出比赛,然后从那里开始。

parseLine :: Text -> IO (Either String Text)
parseLine x = do
  let xStr = convertString x :: String
  case parseFile xStr of
    Just z -> fileRef z
    Nothing -> case parseDiff xStr of
        Just z -> gitDiff z
        Nothing -> case parseTag xStr of
          Just _ -> tagMatch xStr
          Nothing -> return2x x
    where parseFile = parse' (parseFileReference) "file reference"
          parseDiff = parse' (parseGitDiffReference) "git diff tag"
          parseTag  = parse' (parsePossibleTag) "possible tag"
          fileRef z = do
                print z
                fileReferenceContent z >>= \case
                      Just v' -> return2x $ "```\n" <> v' <> "```"
                      Nothing -> return $ Left "Unable to parse"
          gitDiff z = do
                print z
                return2x ""
          tagMatch xStr = do
                return (Left $ "Tag that failed to match: " ++ xStr)

现在,魔法发生了。 Alternative的{​​{1}}将选择第一个成功的匹配。因此,我们将使用Maybe,它使用asum实例汇总了备选列表。

Alternative

最后,我们留下parseLine :: Text -> IO (Either String Text) parseLine x = do let xStr = convertString x :: String let result = asum [ fileRef <$> parseFile xStr, gitDiff <$> parseDiff xStr, tagMatch xStr <$ parseTag xStr -- Not a typo, the operator is (<$) since you ignore the value ] maybe failure id result where ... failure = return2x x 失败result :: Maybe (IO (Either String Text)). If it exists, we want to return it unmodified. If it doesn't, we want to fall back to可能`,这比这里的完整案例陈述更紧凑。

最后,在. So we use中,我们可以将&#34;自定义错误消息的模式分解为失败&#34;。

fileRef

然后该陈述变为

maybeToEither :: a -> Maybe b -> Either a b
maybeToEither x Nothing  = Left x
maybeToEither _ (Just y) = Right y

所以最终的代码看起来像是

fileRef z = do
     print z
     value <- maybeToEither "Unable to parse" <$> fileReferenceContent z
     return $ (\v' -> "```\n" <> v' <> "```") <$> value

它不比原始代码短,但它具有较少的嵌套大小写替代方案,并且肯定会更线性地读取。