Haskell 试图摆脱嵌套表达式

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

我有一个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"

你可以用所谓的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"