我有一个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'
答案 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)
首先让我们为Object
和String
定义提取器(我不知道您需要的确切类型,但这应该是显而易见的):
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
阻止,我建议不需要getString
和getObject
:
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)