如何在Servant Handler中结构化代码以尽早返回Either计算错误?
这个需求其实挺常见的——在Servant的Handler里串联多个返回Either的函数,还要衔接IO计算,核心就是要让错误一出现就终止流程,别做无用功。我来给你一步步拆解怎么实现:
第一步:统一错误类型,把Either值提升到Handler
首先,Servant的Handler是ExceptT的newtype包装,自带MonadError实例,所以我们可以利用hoistEither函数把Either值直接转换成Handler操作。不过要注意,你现有函数返回的是Either String Int,而最终IO计算返回的是Either SomeError Text,最好先定义一个统一的应用错误类型,方便后续处理:
-- 自定义统一的应用错误类型,覆盖两种错误来源 data AppError = AppErrorFromString String | AppErrorFromSomeError SomeError deriving (Show) -- 实现Servant所需的类型实例,让错误能转成HTTP响应(根据你的实际需求调整) instance ToHttpApiData AppError where toHttpApiData (AppErrorFromString s) = toHttpApiData s toHttpApiData (AppErrorFromSomeError e) = toHttpApiData (show e)
然后写个辅助函数,把Either String a转成Handler a,遇到Left就直接抛出错误:
import Control.Monad.Except (hoistEither) import Servant (Handler) liftEitherToHandler :: Either String a -> Handler a liftEitherToHandler = hoistEither . first AppErrorFromString
第二步:串联三个Either函数,实现尽早错误返回
因为Handler是Monad,用do notation就能自然实现“出错即终止”:如果三个函数里任何一个返回Left,liftEitherToHandler会抛出错误,后续代码直接跳过,错误被传递出去。
如果三个函数之间没有依赖关系,也可以用Applicative风格,代码更简洁:
方式一:Do Notation(适合有依赖的场景)
fullHandler :: Handler Text fullHandler = do -- 调用第一个函数,出错直接返回 int1 <- liftEitherToHandler yourFirstEitherFunc -- 调用第二个函数,只有第一个成功才会执行 int2 <- liftEitherToHandler yourSecondEitherFunc -- 调用第三个函数,前两个成功才会执行 int3 <- liftEitherToHandler yourThirdEitherFunc -- 三个Int都拿到后,执行最终计算 runFinalCalc int1 int2 int3
方式二:Applicative风格(适合无依赖的场景)
fullHandler :: Handler Text fullHandler = do -- 三个函数并行验证(其实是按顺序,但只要一个出错就终止),同时拿到三个Int (int1, int2, int3) <- (,,) <$> liftEitherToHandler yourFirstEitherFunc <*> liftEitherToHandler yourSecondEitherFunc <*> liftEitherToHandler yourThirdEitherFunc runFinalCalc int1 int2 int3
第三步:处理最终的IO计算
接下来把那个Int -> Int -> Int -> IO (Either SomeError Text)的计算整合到Handler里。我们先用liftIO把IO操作拉进Handler,再用hoistEither处理里面的Either值:
runFinalCalc :: Int -> Int -> Int -> Handler Text runFinalCalc a b c = do -- 执行IO计算 eitherResult <- liftIO $ yourFinalCalculation a b c -- 把SomeError转成统一的AppError,出错就抛出 hoistEither $ first AppErrorFromSomeError eitherResult
为什么这样能实现尽早返回?
Handler的MonadError实例会自动处理错误传递:只要liftEitherToHandler遇到Left值,整个do块(或Applicative组合)就会立即终止,错误被包装成Handler的错误类型返回,不会执行后续的函数调用或IO计算,完美符合你“尽早返回错误”的要求。
内容的提问来源于stack exchange,提问作者beta

