用EitherT累积错误 [英] Accumulating errors with EitherT

查看:126
本文介绍了用EitherT累积错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下一个小型样例应用的一个web API,它需要一个巨大的JSON文档,并且应该分析它并分析每个部分的错误消息。



以下代码是使用EitherT(和错误包)的工作示例。然而,问题是EitherT打破了第一个左边的计算,只是返回它看到的第一个错误。我想要的是一个错误消息的列表,所有这些都是可能的。例如,如果 runEitherT 中的第一行失败,那么没有什么可以做的。但是如果第二行失败,那么我们仍然可以尝试运行后续行,因为它们在第二行没有数据依赖关系。所以我们理论上可以一次性生成(不一定全部)错误消息。



是否可以懒惰地运行所有的计算并返回我们可以找到所有的错误信息?

  { - #LANGUAGE OverloadedStrings# - } 

模块Main

import Data.ByteString.Lazy.Char8(pack)
import Web.Scotty as S
import Network.Wai.Middleware.RequestLogger
import Data。 Aeson
import Data.Aeson.Types
import Control.Lens hiding((。=),(??))
import Data.Aeson.Lens
import qualified Data.Text作为T
import Control.Error
import Control.Applicative
import qualified Data.HashMap.Strict as H
import Network.HTTP.Types

data TypeOne = TypeOne T.Text TypeTwo TypeThree
派生(显示)

数据TypeTwo = TypeTwo Double
派生(显示)

数据TypeThree = TypeThree Double
派生(显示)

main :: IO()
main = scotty 3000 $ do
中间件logStdoutDev

post/ pdor$ do
api_key< - paramapi_key
input< - paraminput

typeOne< - runEitherT $ do
result< - (decode(pack input):: Maybe Value)? 无法解析输入JSON文件格式错误
typeTwoObj< - (result ^?keytypeTwo)? 在JSON文件中找不到关键字两个。
typeThreeObj< - (result ^?keytypeThree)? 在JSON文档中找不到关键类型三。
name< - (result ^?keyname。_String)? 无法在JSON文档中找到密钥名称。
typeTwo< - hoistEither $ prependLeft解析TypeTwo时出错:$ parseEither jsonTypeTwo typeTwoObj
typeThree< - hoistEither $ prependLeft解析TypeThree时出错:$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b return $ TypeOne name typeTwo typeThree

case type
的一个left errorMsg - > do
_< - status badRequest400
S.json $ object [error。= errorMsg]
右_ - >
- 用解析的Haskell类型
S.json $ object [api_key。=(api_key :: String),message。=(success:: String)]

prependLeft :: String - >字符串a - >任何一个字符串a
prependLeft msg(Left s)= Left(msg ++ s)
prependLeft _ x = x

jsonTypeTwo :: Value - >解析器TypeTwo
jsonTypeTwo(Object v)= TypeTwo< $> v。:val
jsonTypeTwo _ = fail $TypeTwo没有数据

jsonTypeThree :: Value - >解析器TypeThree
jsonTypeThree(Object v)= TypeThree< $> v。:val
jsonTypeThree _ = fail $TypeThree没有数据

如果任何人有一些,也可以重构建议。

解决方案

正如我在评论中提到的那样,您至少有两种累积错误的方法。下面我详细说明那些。我们需要这些导入:

  import Control.Applicative 
import Data.Monoid
import Data .se



TheseT monad transformer



免责声明: TheseT 被称为 ChronicleT 这些



查看 这些数据类型

  data这些ab = This a |那个b |这些ab 

这里这个对应于 数据类型。 这些数据构造函数是能够为 Monad 实例的累积功能:它包含两个结果(类型为$ code > b )和以前错误的集合( a 的集合)。



利用已经存在的定义这些数据类型,我们可以轻松地创建 ErrorT 类似的monad变换器:

  newtype TheseT ema = TheseT {
runTheseT :: m(These ea)
}

TheseT Monad 以下方式:

 实例Functor m => Functor(TheseT e m)其中
fmap f(TheseT m)= TheseT(fmap(fmap f)m)

instance(Monoid e,Applicative m)=>其中
pure x = TheseT(pure(pure x))
TheseT f *这些T x = TheseT(liftA2(*))f x)

实例(Monoid e,Monad m)=> Monad(TheseT em)其中
返回x = TheseT(return(return x))
m>> = f = TheseT $ do
t< - runTheseT m
case t的
这个e - > return(this e)
那个x - > runTheseT(f x)
这些_ x - > do
t'< - runTheseT(fx)
return(t>> t') - 这是错误连接的地方



适用累积 ErrorT



免责声明:这种方法更容易适应,因为您已经在 m(EEA) newtype wrapper ,但它只适用于适用设置。



如果实际代码只使用 接口我们可以通过 ErrorT 更改其应用实例。



我们从非变压器版本开始:

  data Accum ea = ALeft e | ARight a 

实例Functor(Accum e)其中
fmap f(ARight x)= ARight(fx)
fmap _(ALeft e)= ALeft e

instance Monoid e =>适用(Accum e)其中
pure = ARight
ARight f * ARight x = ARight(f x)
ALeft e * ALeft e'= ALeft(e e')
ALeft e * _ = ALeft e
_< *>注意,当定义< *> / code>我们知道如果双方都是 ALeft ,因此可以执行<> code>。如果我们尝试定义相应的 Monad 实例,我们将失败:

  instance Monoid e => Monad(Accum e)其中
return = ARight
ALeft e>> = f = - 我们不能应用f

所以我们可能只有 Monad 实例是或者。但是 ap < *> 不同:

 留下< *>左b≡左(a< b)
留下一个`ap`左b≡左a

所以我们只能使用 Accum 作为适用



现在我们可以根据 Accum 定义应用变压器:

  newtype AccErrorT ema = AccErrorT {
runAccErrorT :: m(Accum ea)
}

实例(Functor m )=> Functor(AccErrorT e m)其中
fmap f(AccErrorT m)= AccErrorT(fmap(fmap f)m)

instance(Monoid e,Applicative m)=>应用(AccErrorT e m)其中
pure x = AccErrorT(pure(pure x))
AccErrorT f * AccErrorT x = AccErrorT(liftA2(*)fx)

请注意, AccErrorT em 基本上是 Compose m(Accum e)






编辑:



AccError 被称为 AccValidation 验证


I have the following little mini-sample application of a web API that takes a huge JSON document and is supposed to parse it in pieces and report error messages for each of the pieces.

Following code is a working example of that using EitherT (and the errors package). However, the problem is that EitherT breaks the computation on the first Left encountered and just returns the first "error" it sees. What I would like is a list of error messages, all that are possible to produce. For instance, if the first line in runEitherT fails then there's nothing more that can be done. But if the second line fails then we can still try to run subsequent lines because they have no data dependency on the second line. So we could theoretically produce more (not necessarily all) error messages in one go.

Is it possible to run all the computations lazily and return all the error messages we can find out?

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.ByteString.Lazy.Char8 (pack)
import Web.Scotty as S
import Network.Wai.Middleware.RequestLogger
import Data.Aeson
import Data.Aeson.Types
import Control.Lens hiding ((.=), (??))
import Data.Aeson.Lens
import qualified Data.Text as T
import Control.Error
import Control.Applicative
import qualified Data.HashMap.Strict as H
import Network.HTTP.Types

data TypeOne = TypeOne T.Text TypeTwo TypeThree
  deriving (Show)

data TypeTwo = TypeTwo Double
  deriving (Show)

data TypeThree = TypeThree Double
  deriving (Show)

main :: IO ()
main = scotty 3000 $ do
  middleware logStdoutDev

  post "/pdor" $ do
    api_key <- param "api_key"
    input   <- param "input"

    typeOne <- runEitherT $ do
      result       <- (decode (pack input) :: Maybe Value) ?? "Could not parse. Input JSON document is malformed"
      typeTwoObj   <- (result ^? key "typeTwo")            ?? "Could not find key typeTwo in JSON document."
      typeThreeObj <- (result ^? key "typeThree")          ?? "Could not find key typeThree in JSON document."
      name         <- (result ^? key "name" . _String)     ?? "Could not find key name in JSON document."
      typeTwo      <- hoistEither $ prependLeft "Error when parsing TypeTwo: " $ parseEither jsonTypeTwo typeTwoObj
      typeThree    <- hoistEither $ prependLeft "Error when parsing TypeThree: " $ parseEither jsonTypeThree typeThreeObj

      return $ TypeOne name typeTwo typeThree

    case typeOne of
      Left errorMsg -> do
        _ <- status badRequest400
        S.json $ object ["error" .= errorMsg]
      Right _ ->
        -- do something with the parsed Haskell type
        S.json $ object ["api_key" .= (api_key :: String), "message" .= ("success" :: String)]

prependLeft :: String -> Either String a -> Either String a
prependLeft msg (Left s) = Left (msg ++ s)
prependLeft _ x = x

jsonTypeTwo :: Value -> Parser TypeTwo
jsonTypeTwo (Object v) = TypeTwo <$> v .: "val"
jsonTypeTwo _ = fail $ "no data present for TypeTwo"

jsonTypeThree :: Value -> Parser TypeThree
jsonTypeThree (Object v) = TypeThree <$> v .: "val"
jsonTypeThree _ = fail $ "no data present for TypeThree"

Also open to refactoring suggestions if anyone has some.

解决方案

As I mentioned in a comment, you have at least 2 ways of accumulating error. Below I elaborate on those. We'll need these imports:

import Control.Applicative
import Data.Monoid
import Data.These

TheseT monad transformer

Disclaimer: TheseT is called ChronicleT in these package.

Take a look at the definition of These data type:

data These a b = This a | That b | These a b

Here This and That correspond to Left and Right of Either data type. These data constructor is what enables accumulating capability for Monad instance: it contains both result (of type b) and a collection of previous errors (collection of type a).

Taking advantage of already existing definition of These data type we can easily create ErrorT-like monad transformer:

newtype TheseT e m a = TheseT {
  runTheseT :: m (These e a)
}

TheseT is an instance of Monad in the following way:

instance Functor m => Functor (TheseT e m) where
  fmap f (TheseT m) = TheseT (fmap (fmap f) m)

instance (Monoid e, Applicative m) => Applicative (TheseT e m) where
  pure x = TheseT (pure (pure x))
  TheseT f <*> TheseT x = TheseT (liftA2 (<*>) f x)

instance (Monoid e, Monad m) => Monad (TheseT e m) where
  return x = TheseT (return (return x))
  m >>= f = TheseT $ do
    t <- runTheseT m
    case t of
      This  e   -> return (This e)
      That    x -> runTheseT (f x)
      These _ x -> do
        t' <- runTheseT (f x)
        return (t >> t')  -- this is where errors get concatenated

Applicative accumulating ErrorT

Disclaimer: this approach is somewhat easier to adapt since you already work in m (Either e a) newtype wrapper, but it works only in Applicative setting.

If the actual code only uses Applicative interface we can get away with ErrorT changing its Applicative instance.

Let's start with a non-transformer version:

data Accum e a = ALeft e | ARight a

instance Functor (Accum e) where
  fmap f (ARight x) = ARight (f x)
  fmap _ (ALeft e)  = ALeft e

instance Monoid e => Applicative (Accum e) where
  pure = ARight
  ARight f <*> ARight x = ARight (f x)
  ALeft e  <*> ALeft e' = ALeft (e <> e')
  ALeft e  <*> _        = ALeft e
  _        <*> ALeft e  = ALeft e

Note that when defining <*> we know if both sides are ALefts and thus can perform <>. If we try to define corresponding Monad instance we fail:

instance Monoid e => Monad (Accum e) where
  return = ARight
  ALeft e >>= f = -- we can't apply f

So the only Monad instance we might have is that of Either. But then ap is not the same as <*>:

Left a <*>  Left b  ≡  Left (a <> b)
Left a `ap` Left b  ≡  Left a

So we only can use Accum as Applicative.

Now we can define Applicative transformer based on Accum:

newtype AccErrorT e m a = AccErrorT {
  runAccErrorT :: m (Accum e a)
}

instance (Functor m) => Functor (AccErrorT e m) where
  fmap f (AccErrorT m) = AccErrorT (fmap (fmap f) m)

instance (Monoid e, Applicative m) => Applicative (AccErrorT e m) where
  pure x = AccErrorT (pure (pure x))
  AccErrorT f <*> AccErrorT x = AccErrorT (liftA2 (<*>) f x)

Note that AccErrorT e m is essentially Compose m (Accum e).


EDIT:

AccError is known as AccValidation in validation package.

这篇关于用EitherT累积错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆