オリジナルの例を使用して作業します。あなたの最初のものがモナド変圧器のコードのように見えるのに対し、あなたの2番目のような工夫された例はうまく単純化することはまずありません。
あなたが実り多いように終わらないように見えるような一般的な「if-then-case」ステートメントを探してください。これは、ケースステートメントが実際には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
呼び出します。これは、まだ、何かを改善するものではありません。しかし、もう少しリファクタリングしてみましょう。その後、私たちはそれが何を得るかを見ていきます。私はすべての "成功"のケース(前にRight z
であり、新しいparse'
コードでJust z
である)が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)
ここで魔法が発生します。 Maybe
のAlternative
は最初に成功した一致を選択します。したがって、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
、我々はここで完全なcase文よりもコンパクトである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
maybe`、と左にしています。
最後に、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
だから、最終的なコードが
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
maybeToEither :: a -> Maybe b -> Either a b
maybeToEither x Nothing = Left x
maybeToEither _ (Just y) = Right y
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
]
maybe failure id result
where parseFile = parse' (parseFileReference) "file reference"
parseDiff = parse' (parseGitDiffReference) "git diff tag"
parseTag = parse' (parsePossibleTag) "possible tag"
fileRef z = do
print z
value <- maybeToEither "Unable to parse" <$> fileReferenceContent z
return $ (\v' -> "```\n" <> v' <> "```") <$> value
gitDiff z = do
print z
return2x ""
tagMatch xStr = do
return (Left $ "Tag that failed to match: " ++ xStr)
failure = return2x x
のように見えるそれは、元のコードより短くはないが、それはあまりネストされたケースの選択肢を持っており、確かにそれなりましたより線形に読み取ります。
私がここで質問する最初のことは、これがすべて「IO」である理由です。真ん中には任意のprintステートメント(デバッグステートメントなど)があり、その他のI/Oはありません。 'IO'モナド依存関係を取り除くことで、おそらく' Maybe'と 'Either'の間のいくつかの削減が可能になります。 –
IOの理由は、 'fileReferenceContent'が' FileReference - > IO(Maybe Text) '型であるためです。私が助けてくれればもっと簡単な例を提供できるかもしれません。 –
'' IO(Maybe(Either String Text)) 'を作って、" Nothing "は"リストで次のケースを試してください "を意味し、" Just "は"ここで停止して、結果が得られる "という意味です。 – chi