Haskell 试图摆脱嵌套表达式
我有一个yaml文件:Haskell 试图摆脱嵌套表达式,haskell,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 -> c
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"
你可以用所谓的a来写
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)
read123::IO(字符串,字符串,字符串)
read123=do
rawConfig我建议您使用适当的数据类型来存储YAML数据。Michael Snoyman的库重用了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
这可以使用Maybe
单子简化
让我们关注一下这个片段
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)
首先,让我们为对象
和字符串
定义提取器(我不知道您需要的确切类型,但这应该是显而易见的):
现在可以用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)
然后测试(仅为0)没有任何失败。\uu
模式仅适用于最外层的案例,而不适用于最内层的案例
另外,我建议将纯部分(在纯函数中处理数据)从IO
部分中分离出来。我强烈建议Sibi的答案生成数据类型和您自己的实例是最可读和可维护的解决方案。我仍将介绍另一种进展:
步骤1:演示
一定要用可编译的代码发布问题。这包括pragma和imports
{-# 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:单子是程序员最好的朋友
考虑使用maybe 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
read123':IO(文本,文本,文本)
read123'=
可能会(文本,文本,文本)
myParse rawConfig=do
res1上面关于如何使用Maybe
monad的讨论对于我来说有点太复杂了,无法在评论中解释我的想法,所以这里是我如何将其组合起来的(基于最初的问题,Petr Pudlák的do
块,以及我的建议,即不需要getString
和getObject
):
read123=do——这个do块在IO Monad中
如果你的代码工作,请考虑问你的问题。这个问题似乎完全适合于堆栈溢出对我来说,请求是非常具体的(去掉嵌套的表达式),而不仅仅是“帮助我改进我的代码”。因为它读取一个文件,所以返回类型必须是IO,不是吗?既然它是IO,你就不能用它了“@AlexanderSupertramp这是一个基本的误解。您不需要getObject
和getString
函数。你可以只做objectres3@ØrjanJohansen,你确定吗?我如预期的那样,得到了错误无法匹配类型
Maybe'与IO'预期类型:IO值实际类型:Maybe值
@AlexanderSupertramp您的更新与我说的无关,这是关于PetrPudlák的代码。我需要补充的是,这依赖于的函数,可能是的函数,导致模式匹配失败。在这种情况下,这是完全合理的。另见@PetrPudlák许多人认为在这种情况下,fail
应该被mzero
或其他一些不被强迫进入Monad
类的东西所取代,但我不认为有那么多人认为Maybe
是错误的,[]
和类似的Monad
s来支持do
中的模式匹配。谢谢。出于好奇,在你的方法中是否有任何方法不使用第二个do
,只使用>=
或>
;对于do
中的模式匹配,官方是非常嵌套的,比如let ok1(Object res2)=let ok2(Object res3)=。。。;在LHashMap.lookup“base123”res2>>=ok2中,ok2=失败“…”;在rawConfig>>=ok1
中,ok1=失败“…”。当模式可能失败时,你就不能真正简化那么多。@AlexanderSupertramp等等,我刚刚意识到PetrPudlák最初使用的getString
和getObject
就是一种解决这个问题的方法,因为这些函数基本上包含了必要的ok
函数的一部分。因此,从他的方法中,你得到了一个更漂亮的去糖化:rawConfig>=getObject>=\res2->LHashMap.lookup“base123”res2>=getObject>=\res3->……
。我决定采用你的方法。还有一件事:你能解释一下parseJSON(Object v)=Base…
中表达式的“求值”顺序吗,特别是哪个函数
首先求值:第一个还是第二个?@AlexanderSupertramp对不起,我不能正确理解你的问题。整个Base…
是一个函数的应用,其结果是解析器Base
的类型。如果这不能解决你的疑问,请告诉我。
getString :: (MonadPlus m) => ... -> m String
getString (String o) = return o
getString _ = mzero
getObject :: ...
getObject (Object o) = return o
getObject _ = mzero
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)
test x y =
case x of
Just x' ->
case y of
Just y' -> True
_ -> False
{-# 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
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
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)
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"