试图摆脱嵌套的表达式

时间:2014-07-26 16:43:34

标签: haskell

我有一个yaml文件:

base123:
  key1: "key1"
  key2: "key2"
  key3: "key3"

和代码,它被允许从中读取所有3个值:

read123 :: IO (String, String, String)
read123 = do
  rawConfig <- Y.decodeFile "config.yml" :: IO (Maybe Y.Value)
  case rawConfig of
    Just res1 ->
      case res1 of
        Object res2 ->
          case (LHashMap.lookup "base123" res2) of
            Just (Object res3) -> 
              case (LHashMap.lookup "key1" res3) of
                Just (String key1) -> 
                  case (LHashMap.lookup "key2" res3) of
                    Just (String key2) -> 
                      case (LHashMap.lookup "key3" res3) of
                        Just (String key3) -> return (key1, key2, key3)
        _ -> error "some error"        

    Nothing -> error "error123"

似乎工作正常。但我相信,必须有一种方法可以在不使用Lens的情况下摆脱嵌套表达式。有没有?或者有什么方法可以更简单地做同样的事情吗?

更新

  rawConfig <- Y.decodeFile "config.yml" :: IO (Maybe Y.Value)
  case rawConfig of
    Just (Object res1) -> LHashMap.lookup "base123" res1
    Nothing -> error "error"

  return ("", "", "") -- stub 

错误:

Couldn't match type `Maybe' with `IO'
    Expected type: IO Value
      Actual type: Maybe Value
    In the return type of a call of `LHashMap.lookup'

5 个答案:

答案 0 :(得分:6)

我建议您使用正确的数据类型来存储YAML数据。 Michael Snoyman的yaml库重用了aeson中的大部分API。所以它与你使用aeson包的方式非常相似。以下是一个有效的示例代码:

{-# LANGUAGE OverloadedStrings #-}

import Control.Applicative
import Control.Monad (mzero)
import Data.Text
import Data.Yaml

data Base = Base {
  key1 :: Text,
  key2 :: Text,
  key3 :: Text
  } deriving (Show)

instance FromJSON Base where
  parseJSON (Object v) = Base <$>
                         ((v .: "base123") >>= (.: "key1")) <*>
                         ((v .: "base123") >>= (.: "key2")) <*>
                         ((v .: "base123") >>= (.: "key3"))

  parseJSON _ = mzero

main = do
  b <- decodeFile "/home/sibi/yaml.yml" :: IO (Maybe Base)
  print b

在ghci:

λ> main
Just (Base {key1 = "key1", key2 = "key2", key3 = "key3"})

答案 1 :(得分:5)

这可以使用Maybe monad进行简化。

让我们关注代码段

case (LHashMap.lookup "base123" res2) of
  Just (Object res3) -> 
    case (LHashMap.lookup "key1" res3) of
      Just (String key1) -> 
        case (LHashMap.lookup "key2" res3) of
          Just (String key2) -> 
            case (LHashMap.lookup "key3" res3) of
              Just (String key3) -> return (key1, key2, key3)

首先让我们为ObjectString定义提取器(我不知道您需要的确切类型,但这应该是显而易见的):

getString :: (MonadPlus m) => ... -> m String
getString (String o) = return o
getString _          = mzero

getObject :: ...
getObject (Object o) = return o
getObject _          = mzero

现在可以使用Maybe monad:

中的计算替换该代码段
do -- in the Maybe moned
  res3 <- getObject =<< LHashMap.lookup "base123" res2
  key1 <- getString =<< LHashMap.lookup "key1" res3
  key2 <- getString =<< LHashMap.lookup "key2" res3
  key3 <- getString =<< LHashMap.lookup "key2" res3
  return (key1, key2, key3)

如果一切顺利,您将获得Just (...),否则Nothing(您可能会变成IO错误。)


除此之外:请注意,您的嵌套case表达式无法按预期工作,因为它们会形成不完整的模式。例如,如果您定义

test x y =
  case x of
    Just x' ->
      case y of
        Just y' -> True
    _ -> False

然后test (Just 0) Nothing失败。 _模式仅适用于最外层case,而不适用于内层模式。

此外,我建议从IO部分拆分纯部分(在纯函数中处理数据)。

答案 2 :(得分:3)

上面关于如何使用Maybe monad的讨论对于我在评论中解释我的想法有点过于复杂,所以我可以将它放在一起(基于原始问题,Petr Pudlák的do阻止,我建议不需要getStringgetObject

read123 = do -- this do block is in the IO Monad
  rawConfig <- Y.decodeFile "config.yml" :: IO (Maybe Y.Value)
  case
    do -- this do block is in the Maybe Monad
      Object res2 <- rawConfig
      Object res3 <- LHashMap.lookup "base123" res2
      String key1 <- LHashMap.lookup "key1" res3
      String key2 <- LHashMap.lookup "key2" res3
      String key3 <- LHashMap.lookup "key2" res3
      return (key1, key2, key3)
    of -- now we are matching on the result of the Maybe do block, to use in the outer IO block
      Just tup -> return tup
      Nothing  -> error "Some error"

答案 3 :(得分:2)

您可以使用所谓的Pattern Guard并编写

read123 :: IO (String, String, String)
read123 = do
   rawConfig <- Y.decodeFile "config.yml" :: IO (Maybe Y.Value)
   return $ decode rawConfig
   where decode conf 
         | Just (Object res2) <- conf
         , Just (Object res4) <- LHasMap.lookup "base123" res4
         , Just (String ke1) <- LHashmap.Lookup "key1" res4 
         etc ...
         -> (key1, key2, key3)

等...

您可以直接在case语句中执行此操作,而不是使用guard。

答案 4 :(得分:2)

我强烈推荐Sibi的答案 - 使数据类型和您自己的实例成为最易读和可维护的解决方案。我还会提出另一种选择:

第1步:演示文稿

请务必使用可编译代码发布问题。这包括编译指示和导入。

{-# LANGUAGE OverloadedStrings #-}
module Foo where
import Data.Yaml as Y
import Data.HashMap.Lazy as LHashMap
import Data.Text

-- For Step 3
import Data.Maybe
import Data.Aeson.Lens
import Control.Lens

第2步:Monads是程序员最好的朋友

考虑使用may monad,正如其他人所建议的那样:

read123' :: IO (Text,Text,Text)
read123' =
 do rawConfig <- Y.decodeFile "foo.yml" :: IO (Maybe Y.Value)
    return $ maybe (error "error123") id $ myParse rawConfig
  where
  myParse :: Maybe Y.Value -> Maybe (Text,Text,Text)
  myParse rawConfig = do
    res1 <- rawConfig
    res2 <- maybe (error "someError") Just (objMaybe res1)
    res3 <- objMaybe =<< LHashMap.lookup "base123" res2
    key1 <- strMaybe =<< LHashMap.lookup "key1" res3
    key2 <- strMaybe =<< LHashMap.lookup "key2" res3
    key3 <- strMaybe =<< LHashMap.lookup "key3" res3
    return (key1,key2,key3)

-- Helpers make things more readable
objMaybe :: Y.Value -> Maybe (LHashMap.HashMap Text Y.Value)
objMaybe (Object x) = Just x
objMaybe _          = Nothing

strMaybe :: Y.Value -> Maybe Text
strMaybe (String x) = Just x
strMaybe _          = Nothing

第3步:镜头

考虑使用镜头和不错的变量名称。镜头使得访问嵌套结构变得更加容易。另请参阅the tutorial

read123'' :: IO (Text,Text,Text)
read123'' = (fromMaybe (error "error123") . myParse) `fmap` Y.decodeFile "foo.yml"
  where
  myParse :: Maybe Y.Value -> Maybe (Text,Text,Text)
  myParse Nothing = error "someError"
  myParse (Just cfg) = do
    base <- cfg ^? ix "base123"
    key1 <- base ^? ix "key1" . _String
    key2 <- base ^? ix "key2" . _String
    key3 <- base ^? ix "key3" . _String
    return (key1,key2,key3)