Допустим, у меня есть очень сложный набор вычислений вида 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
может выдавать исключения! Мы быстро подбрасываем некоторый код и можем поднять исключение типа single в polysemy
. Мы пишем помощника
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
. Мы не можем погрузиться сколь угодно глубоко в монаду, содержащую исключения, и передать ее в хорошем и чистом виде. Если по какой-либо причине мы обнаружим угловой случай, когда computation
может выдать MyException'
, у нас нет никакого способа вставить поддержку этого и перехватить ее в другом месте нашего кода!
Есть ли что-то, чего мне не хватает вбиблиотека, которая позволяет мне сделать это? Застрял ли я иметь дело с исключениями в IO
? Будем весьма благодарны за некоторые указания о том, куда идти дальше и сделать это достаточно полиморфным.