我偶尔遇到一个小问题,我有一组嵌套的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)
。
答案 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 z
且Just 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
它不比原始代码短,但它具有较少的嵌套大小写替代方案,并且肯定会更线性地读取。