Ответ 1
Вы можете работать в IO
, возвращать значение типа IO (Either ServantErr r)
в конце и обернуть его в ExceptT
, чтобы он соответствовал типу обработчика. Это позволит вам использовать bracket
обычно в IO
. Одна из проблем с этим подходом заключается в том, что вы теряете "автоматическое управление ошибками", которое предоставляет ExceptT
. То есть, если вы потерпите неудачу в середине обработчика, вам нужно будет выполнить явное сопоставление шаблонов на Either
и тому подобное.
Вышеупомянутое в основном переопределяет экземпляр MonadTransControl
для ExceptT
, который
instance MonadTransControl (ExceptT e) where
type StT (ExceptT e) a = Either e a
liftWith f = ExceptT $ liftM return $ f $ runExceptT
restoreT = ExceptT
monad-control отлично работает при подъеме функций, таких как bracket
, но у него есть нечетные угловые случаи с функциями вроде следующего (взято из это сообщение в блоге):
import Control.Monad.Trans.Control
callTwice :: IO a -> IO a
callTwice action = action >> action
callTwice' :: ExceptT () IO () -> ExceptT () IO ()
callTwice' = liftBaseOp_ callTwice
Если мы перейдем к callTwice'
к действию, которое что-то печатает и сбой сразу после
main :: IO ()
main = do
let printAndFail = lift (putStrLn "foo") >> throwE ()
runExceptT (callTwice' printAndFail) >>= print
Он печатает "foo" два раза в любом случае, даже если наша интуиция говорит, что он должен остановиться после первого выполнения действия.
Альтернативный подход - использовать библиотеку resourcet
и работать в монаде ExceptT ServantErr (ResourceT IO) r
. Вам нужно будет использовать функции resourcet
, такие как allocate
вместо bracket
, и адаптировать монаду в конце, например:
import Control.Monad.Trans.Resource
import Control.Monad.Trans.Except
adapt :: ExceptT ServantErr (ResourceT IO) r -> ExceptT err IO r
adapt = ExceptT . runResourceT . runExceptT
или как:
import Control.Monad.Morph
adapt' :: ExceptT err (ResourceT IO) r -> ExceptT err IO r
adapt' = hoist runResourceT