我正在练习与我的错误处理保持一致,并且我一直希望看到我编写的代码开始缩小。但是我构建了一个具有领域意义的持久性函数,而我必须编写的代码量只是为了进行 monad 处理和自定义错误处理是惊人的。
- 对于“编程错误”,我只是调用
error "assertion blown"
- 对于真正平凡的事情,我返回 Nothing(请求的对象不存在)
- 对于应该处理的错误,我
Either E V
通过创建一个Control.Monad.Error
实例来处理它来返回或等效。
我在我的应用程序中有多个函数,我称之为原语,但它们可以捕获某些错误并将引发它们我抛出 DBError 类型的值。所以,我这样定义它们:
data DBError = ConversionError ConvertError
| SaveError String
| OtherError String
deriving (Show, Eq)
instance Error DBError where
noMsg = OtherError "No message found"
strMsg s = OtherError s
type DBMonad = ErrorT DBError IO
selectWorkoutByID :: IConnection a => UUID -> a -> DBMonad (Maybe SetRepWorkout)
insertWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
在调用应用程序级别,Workout 是一个持久保存到数据库的唯一对象,因此应用程序只调用 saveWorkout,它本身以您期望的方式使用 selectWorkoutByID、insertWorkout 和 updateWorkout:
saveWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
saveWorkout workout conn =
r <- liftIO $ withTransaction conn $ \conn -> runErrorT $ do
w_res <- selectWorkoutByID (uuid workout) conn
case w_res of
Just w -> updateWorkout workout conn >> return ()
Nothing -> insertWorkout workout conn >> return ()
case r of
Right _ -> return ()
Left err -> throwError err
这很丑陋。我必须运行并解包 DBMonad,在 IO monad 中运行它,将 IO 提升回 DBMonad,然后检查结果并在 DBMonad 中重新包装结果。
如何使用更少且更易于阅读的代码来做到这一点?
我期望使用我的自定义应用程序 monad 来处理可恢复的错误将帮助我减少我必须编写的代码量,但这恰恰相反!
以下是一些额外的问题:
- 有没有更好的方法来构建应用程序语义错误?
- 我应该改用 Control.Exception 吗?