Извлечение нескольких исключений ввода-вывода в многозначность - PullRequest
4 голосов
/ 06 октября 2019

Допустим, у меня есть очень сложный набор вычислений вида 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? Будем весьма благодарны за некоторые указания о том, куда идти дальше и сделать это достаточно полиморфным.

1 Ответ

1 голос
/ 08 октября 2019

Мы можем решить это с помощью 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))

См. Это Суть для него в действии.

...