将嵌套模式替换为" do"对于Monad

时间:2014-06-30 11:51:20

标签: haskell

我想简化此代码

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson
import Network.HTTP.Types
import Data.Text

getJSON :: String -> IO (Either String Value)
getJSON url = eitherDecode <$> simpleHttp url

--------------------------------------------------------------------
maybeJson <- getJSON "abc.com"
case maybeJson of
  Right jsonValue -> case jsonValue of
      (Object jsonObject) -> 
        case (HashMap.lookup "key123" jsonObject) of
          (Just (String val)) -> Data.Text.IO.putStrLn val
          _ -> error "Couldn't get the key"

      _ -> error "Unexpected JSON"
  Left errorMsg -> error $ "Error in parsing: " ++ errorMsg

使用Monad的do语法

maybeJson <- getJSON "abc.com/123"
let toPrint = do 
                Right jsonValue <- maybeJson
                Object jsonObject <- jsonValue
                Just (String val) <- HashMap.lookup "key123" jsonObject
                return val
case toPrint of
  Just a -> Data.Text.IO.putStrLn a
  _ -> error "Unexpected JSON"

它给了我3个错误:

src/Main.hs:86:19:
    Couldn't match expected type `Value'
                with actual type `Either t0 (Either String Value)'
    In the pattern: Right jsonValue
    In a stmt of a 'do' block: Right jsonValue <- maybeJson


src/Main.hs:88:19:
    Couldn't match expected type `Value' with actual type `Maybe Value'
    In the pattern: Just (String val)
    In a stmt of a 'do' block:
      Just (String val) <- HashMap.lookup "key123" jsonObject


src/Main.hs:88:40:
    Couldn't match type `Maybe' with `Either String'
    Expected type: Either String Value
      Actual type: Maybe Value

Even when I replace 

    Just (String val) <- HashMap.lookup "key123" jsonObject

String val <- HashMap.lookup "key123" jsonObject

我收到了关于Either的另一个类似错误:

Couldn't match type `Maybe' with `Either String'
    Expected type: Either String Value
      Actual type: Maybe Value
    In the return type of a call of `HashMap.lookup'
    In a stmt of a 'do' block:
      String val <- HashMap.lookup "key123" jsonObject

如何解决这些错误?

3 个答案:

答案 0 :(得分:2)

您无法轻松将其简化为单个记号块,因为每个case都匹配不同的类型。第一个是解包either,第二个是Value,第三个是Maybe。表示法通过一种类型将所有内容组合在一起起作用,因此它不能直接应用于此。

可以转换所有情况以使用相同的monad,然后在do-block中将其全部写出来。例如,您可以使用辅助函数来执行第二个和第三个模式匹配,并生成适当的Either。但是,这与你现在所拥有的不同!

事实上,如果我采用这种方法,我只是满足于将两个内部匹配提取到他们自己的where变量中并保留它。试图将整个事物整合成一个monad只是混淆了这个问题;它不是正确的抽象。

相反,您可以实现不同类型的抽象。特别是,考虑使用具有棱镜的lens库来处理嵌套模式匹配。它甚至支持aeson!你想要的功能看起来像这样:

decode :: String -> Maybe Value
decode json = json ^? key "key123"

您还可以将其与更具体的棱镜结合使用,例如,如果您需要字符串值:

decode :: String -> Maybe String
decode json = json ^? key "key123" . _String

这需要解析json,确保它是一个对象并获取指定键的任何内容。唯一的问题是它没有给你一个关于失败原因的有用错误信息;不幸的是,我对镜片不够了解如何解决这个问题(如果可能的话)。

答案 1 :(得分:1)

由于您在IO monad中,所有<-都将假设您正在处理IO操作。当你写

do 
    Right jsonValue <- maybeJson
    Object jsonObject <- jsonValue

你是说jsonValue必须是一个IO动作,就像maybeJson一样。但这种情况并非如此! jsonValue只是一个常规的Either值。这里的说法是使用do-block let而不是<-

do 
    Right jsonValue <- maybeJson
    let Object jsonObject = jsonValue

但是,重要的是要注意,在两个版本的代码中,如果JSON解析失败,则使用不可恢复的error来中止程序。如果你想能够收集错误,基本的想法是将你的值转换为Either(然后使用monad实例为Either以避免有大量嵌套的case表达式)

答案 2 :(得分:1)

因此,Monad的do表达式中的每一行都必须返回该Monadic类型的值。 Monad是一个类型类,而不是一个类型。所以把所有东西都放在一个Monad中并不是一个明智的陈述。

你可以尝试使用Maybe monad中的所有内容编写代码。 假设您已经获取了JSON值:

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson
import Network.HTTP
import qualified Data.Map as M
import Control.Applicative
import qualified Data.HashMap.Strict as HM

--------------------------------------------------------------------
main = do
    maybeJson <- return $ toJSON (M.fromList [("key123","value")] :: M.Map String String)
    ioVal <- return $ do -- The Maybe monad do expression starts here
        maybeJson <- Just maybeJson
        jsonObject <- case maybeJson of
            Object x -> Just x
            _ -> Nothing
        val <- HM.lookup "key123" jsonObject                                                                                                                         
        return val 
    putStrLn $ show ioVal

一旦我们开始在Maybe monad中工作,每个表达式都必须返回Maybe Something值。 Maybe monad的工作方式是Just something的任何内容都是纯粹的something,你可以使用它,但如果你得到Nothing,那么其余的Nothing将跳过代码,您将获得import qualified Network.HTTP as H main = do postData <- return $ H.urlEncodeVars [("someVariable","someValue")] request <- return $ H.postRequestWithBody "http://www.google.com/recaptcha/api/verify" "application/x-www-form-urlencoded" postData putStrLn $ show request -- Make the request (Right response) <- H.simpleHTTP request -- Print status code putStrLn $ show $ H.rspCode response -- Print response putSrLn $ show $ H.rspBody response

坠落的这个属性对于Maybe monad来说是独一无二的。不同的monad表现不同。

您应该在此处阅读有关Monads和IO monad的更多信息:http://www.haskell.org/haskellwiki/Introduction_to_IO

你应该阅读更多关于monad的信息以及它们真正帮助你做的事情: http://learnyouahaskell.com/a-fistful-of-monads (你应该完成前面的章节,然后进入本章。一旦你这样做,你就会对发生的事情有一个非常了解的。)

我还认为你的HTTP请求搞砸了。以下是您可以使用的POST请求示例。

import qualified Data.ByteString.Lazy as LC
import qualified Data.ByteString.Char8 as C
import qualified Data.Aeson as DA

responseBody <- return $ H.rspBody response
responseJSON <- return (DA.decode (LC.fromChunks [C.pack responseBody]) :: Maybe DA.Value)

更新: 使用以下内容可帮助您获取JSON值:

{{1}}

您必须创建一个请求对象才能发出请求。有很多帮手。我的帖子请求是最常见的情况: http://hackage.haskell.org/package/HTTP-4000.0.5/docs/Network-HTTP.html