将多个 IO 异常提取为多义词
Extracting multiple IO exceptions into polysemy
假设我有一些非常复杂的计算集,形式为 computation :: IO a
,我无法修改,因为它来自某些库代码或其他原因。假设我想提供一些类型级别的保证,即我们无法在使用这些计算的过程中发射核导弹,所以我们使用 polysemy
并将所有这些都包装到它自己的 DSL Library
.我们可以天真地解释为
runLibraryIO :: Member (Embed IO) r => Sem (Library ': r) a -> Sem r a
runLibraryIO = interpret $ \case
Computation -> embed computation
-- ...
一切都很好,但是 computation
可能会抛出异常!我们很快抛出了一些代码,并且能够将 单一 类型的异常提升为 polysemy
。我们写 helper
withIOError :: forall e r a . (Exception e, Members '[Embed IO, Error e] r) => IO a -> Sem r a
withIOError action = do
res <- embed $ try action
case res of
Left e -> throw @e e
Right x -> pure x
然后我们的解释器变成了
runLibraryIO :: Members '[Embed IO, Error MyException] r => Sem (Library ': r) a -> Sem r a
runLibraryIO = interpret $ withIOError @MyException . \case
Computation -> computation
-- ...
但我们很快注意到这是不可扩展的。特别是,我们只能解除一种类型的异常,并且仅限于 IO
中的计算。我们不能任意深入到包含异常的 monad 中,然后以一种良好而纯粹的方式将其传递出去。如果出于某种原因我们发现 computation
可能抛出 MyException'
的极端情况,我们无法插入对此的支持并在我们的代码中的其他地方捕获它!
库中是否缺少允许我执行此操作的内容?我是否坚持处理 IO
中的异常?非常感谢关于从这里向前推进并使其充分多态的一些指导。
我们可以用 lower
解释器解决这个问题。谢谢@KingoftheHomeless。
withException :: forall e r a . (E.Exception e, Member IOError r, Member (Error e) r) => Sem r a -> Sem r a
withException action = catchIO @r @e action throw
lowerIOError :: Member (Embed IO) r => (forall x. Sem r x -> IO x) -> Sem (IOError ': r) a -> Sem r a
lowerIOError lower = interpretH $ \case
ThrowIO e -> embed $ E.throwIO e
CatchIO m h -> do
m' <- lowerIOError lower <$> runT m
h' <- (lowerIOError lower .) <$> bindT h
s <- getInitialStateT
embed $ lower m' `E.catch` \e -> lower (h' (e <$ s))
请参阅此 gist 以了解实际效果。
假设我有一些非常复杂的计算集,形式为 computation :: IO a
,我无法修改,因为它来自某些库代码或其他原因。假设我想提供一些类型级别的保证,即我们无法在使用这些计算的过程中发射核导弹,所以我们使用 polysemy
并将所有这些都包装到它自己的 DSL Library
.我们可以天真地解释为
runLibraryIO :: Member (Embed IO) r => Sem (Library ': r) a -> Sem r a
runLibraryIO = interpret $ \case
Computation -> embed computation
-- ...
一切都很好,但是 computation
可能会抛出异常!我们很快抛出了一些代码,并且能够将 单一 类型的异常提升为 polysemy
。我们写 helper
withIOError :: forall e r a . (Exception e, Members '[Embed IO, Error e] r) => IO a -> Sem r a
withIOError action = do
res <- embed $ try action
case res of
Left e -> throw @e e
Right x -> pure x
然后我们的解释器变成了
runLibraryIO :: Members '[Embed IO, Error MyException] r => Sem (Library ': r) a -> Sem r a
runLibraryIO = interpret $ withIOError @MyException . \case
Computation -> computation
-- ...
但我们很快注意到这是不可扩展的。特别是,我们只能解除一种类型的异常,并且仅限于 IO
中的计算。我们不能任意深入到包含异常的 monad 中,然后以一种良好而纯粹的方式将其传递出去。如果出于某种原因我们发现 computation
可能抛出 MyException'
的极端情况,我们无法插入对此的支持并在我们的代码中的其他地方捕获它!
库中是否缺少允许我执行此操作的内容?我是否坚持处理 IO
中的异常?非常感谢关于从这里向前推进并使其充分多态的一些指导。
我们可以用 lower
解释器解决这个问题。谢谢@KingoftheHomeless。
withException :: forall e r a . (E.Exception e, Member IOError r, Member (Error e) r) => Sem r a -> Sem r a
withException action = catchIO @r @e action throw
lowerIOError :: Member (Embed IO) r => (forall x. Sem r x -> IO x) -> Sem (IOError ': r) a -> Sem r a
lowerIOError lower = interpretH $ \case
ThrowIO e -> embed $ E.throwIO e
CatchIO m h -> do
m' <- lowerIOError lower <$> runT m
h' <- (lowerIOError lower .) <$> bindT h
s <- getInitialStateT
embed $ lower m' `E.catch` \e -> lower (h' (e <$ s))
请参阅此 gist 以了解实际效果。