2017-12-03 10 views
3

私は時にはネストされたcase文のセットを持っている小さな問題に遭遇します。これは扱いが面倒です。関数のリスト(例のcase ...ステートメントに相当する)を持っていて、それらのすべてを評価し、最初にパターン(例:Right x)に一致するものを選択するために使用できるテクニック/パターンはありますか?インデントツリーなしでネストされたcase文を処理するにはどうすればよいですか?

私がリストに入れている特定の問題は、それらが必ずしも同じタイプであるとは限りません(私が考える単相性制限)。たとえば、次のよう

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を使用した場合の問題condit私の知る限りパターンマッチはできません。たとえば、if (Just x)です。

+0

私がここで質問する最初のことは、これがすべて「IO」である理由です。真ん中には任意のprintステートメント(デバッグステートメントなど)があり、その他のI/Oはありません。 'IO'モナド依存関係を取り除くことで、おそらく' Maybe'と 'Either'の間のいくつかの削減が可能になります。 –

+0

IOの理由は、 'fileReferenceContent'が' FileReference - > IO(Maybe Text) '型であるためです。私が助けてくれればもっと簡単な例を提供できるかもしれません。 –

+0

'' IO(Maybe(Either String Text)) 'を作って、" Nothing "は"リストで次のケースを試してください "を意味し、" Just "は"ここで停止して、結果が得られる "という意味です。 – chi

答えて

5

(そのまま非網羅的パターンマッチングを維持する)正確な例を翻訳:結果はMaybeとき

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 
     ] 
4

は、少なくとも、これはAlternative例えば明確なユースケースのように見えます。

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

パターンマッチング( 'Eq'の代わりに)を使う必要があるならば、' do'表記やモナド解説を悪用して 'fail '...' 'Nothing'に依存することもできます。 – chi

3

オリジナルの例を使用して作業します。あなたの最初のものがモナド変圧器のコードのように見えるのに対し、あなたの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) 

ここで魔法が発生します。 MaybeAlternativeは最初に成功した一致を選択します。したがって、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 

のように見えるそれは、元のコードより短くはないが、それはあまりネストされたケースの選択肢を持っており、確かにそれなりましたより線形に読み取ります。

関連する問題