[英]Catching exceptions in Scotty / Haskell
我刚刚开始学习Haskell,并坚持如何处理Scotty中的异常。
我有以下基本功能。 它获得一个JSON POST,将其转换为Haskell数据记录,从配置读取器获取postgres连接池,然后将记录插入到数据库中。
create :: ActionT Text ConfigM ()
create = do
a :: Affiliate <- jsonData
pool <- lift $ asks pool
_ <- liftIO $ catchViolation catcher $ withResource pool $ \conn ->
PgSQL.execute conn "INSERT INTO affiliate (id, network, name, status) VALUES (?, ?, ?, ?)"
(slug a, network a, name a, status a)
let s = fromStrict $ unSlug $ slug a
text $ "Created: " `T.append` s
where
catcher e (UniqueViolation "mykey") = throw e --text "Error"
catcher e _ = throw e
这个函数编译好,但是当我更改UniqueViolation以返回文本时,它无法编译。
catcher e (UniqueViolation "mykey") = text "Error"
给出的编译错误是:
Couldn't match type ‘ActionT e0 m0 ()’ with ‘IO Int64’
Expected type: PgSQL.SqlError -> ConstraintViolation -> IO Int64
Actual type: PgSQL.SqlError
-> ConstraintViolation -> ActionT e0 m0 ()
In the first argument of ‘catchViolation’, namely ‘catcher’
In the expression: catchViolation catcher
catchViolation来自Database.PostgreSQL.Simple.Errors并具有以下签名:
catchViolation :: (SqlError -> ConstraintViolation -> IO a) -> IO a -> IO a
我知道问题的一部分是它从PgSQL.execute获取IO Int64但是来自捕手的ActionT但不确定如何解决这些类型或更惯用的方法。
问题是catchViolation
的返回值存在于IO
monad中,但是text
存在于ActionT e IO
monad中,这是一个使用ActionT
monad转换器在IO
上ActionT
monad。
Monad变换器为其基础monad添加了额外的功能。 在ActionT
的情况下,它添加了诸如访问“构造中的响应”之类的东西(这就是text
需要它的原因)。
一种可能的解决方案是将text
的使用从catchViolation
。 相反,make catchViolation
返回一个Either
,然后,一旦返回到ActionT
上下文,就在Either
进行模式匹配以决定要做什么。 就像是:
ei <- liftIO $ catchViolation catcher $ fmap Right $ withResource pool
case ei of
Left str -> text str
Right _ -> return ()
where
catcher e (UniqueViolation "mykey") = return $ Left "some error"
catcher e _ = return $ Left "some other error"
还有另一个解决方案,更强大但不直观。 它发生ActionT
就是一个实例MonadBaseControl
。 这个类型类有一些方法可以让你将monad变换器添加的所有“额外层”隐藏到基本monad的普通值中。 然后,您可以将该值传递给某些回调接受函数,如catchViolation
,然后“弹出”所有额外的图层。
(这有点像将盒子插入其盒子中以便通过海关或其他任何东西,然后让它再次弹出。)
它会是这样的:
control $ \runInBase -> catchViolation
(\_ _ -> runInBase $ text "some error")
(runInBase $ liftIO $ withResource $
.... all the query stuff goes here ...)
我们正在使用控制实用程序功能。 control
为您提供了一个神奇的功能( RunInBase mb
),让您“将插孔插入盒子中”。 也就是说,从ActionT
构造一个IO
值。 然后,您将该值传递给catchViolation
,并且control
负责取消结果中编码的图层,最后返回完整的ActionT
monad。
谢谢你让我与Either一致 。 我发现在Control.Exception中尝试从一个IO创建一个Either:
try :: Exception e => IO a -> IO (Either e a)
我使用尝试从PostgreSQL简单执行函数给我一个[Either SqlError Int64] ,然后使用Control.Arrow.left在PostpsSQL Simple constraintViolation函数上做左侧值的映射,我在https://stackoverflow.com/找到a / 13504032/2658199 。
constraintViolation :: SqlError -> Maybe ConstraintViolation
left :: a b c -> a (Either b d) (Either c d)
然后,这给我下面的模式匹配类型
Either (Maybe ConstraintViolation) Int64
有了上面我已经想到了这个我很满意,但不确定是否惯用或可以进一步改进?
create' :: ActionT Text ConfigM ()
create' = do
a :: Affiliate <- jsonData
pool <- lift $ asks pool
result <- liftIO $ E.try $ withResource pool $ \conn -> do
PgSQL.execute conn "INSERT INTO affiliate (id, network, name, status) VALUES (?, ?, ?, ?)"
(slug a, network a, name a, status a)
let slugT = fromStrict $ unSlug $ slug a
case left constraintViolation result of
Right _ -> text $ "Created: " `T.append` slugT
Left(Just(UniqueViolation "mykey")) -> text "Duplicate key"
_ -> text "Fatal Error"
在建议使用ViewPatterns
后,我将以前的版本简化为以下版本。
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, ViewPatterns #-}
create :: ActionT Text ConfigM ()
create = do
a :: A.Affiliate <- jsonData
pool <- lift $ asks pool
result <- liftIO $ try $ withResource pool $ \conn ->
PgSQL.execute conn "INSERT INTO affiliate (id, network, name, status) VALUES (?, ?, ?, ?)"
(A.slug a, A.network a, A.name a, A.status a)
let slugT = fromStrict $ unSlug $ A.slug a
case result of
Right _ -> text ("Created: " `T.append` slugT) >> status created201
Left (constraintViolation -> Just (UniqueViolation _)) -> text (slugT `T.append` " already exists") >> status badRequest400
Left e -> throw e
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.