Поднимите исключение ввода-вывода в ошибку MonadError с автоматическим отображением

#haskell

Вопрос:

Я пытаюсь написать функцию, которая будет сопоставлять IO исключения MonadError e m с помощью вспомогательного класса, Injection . Однако GHC испытывает проблемы с выводом Exception e10 ограничения (?) в следующем примере:

 #!/bin/env cabal
{- cabal:
build-depends: base
             , mtl
default-extensions: FlexibleInstances
                  , MultiParamTypeClasses
                  , ScopedTypeVariables
-}

import Control.Exception
import Control.Monad.Except

main :: IO ()
main =
  putStrLn $ "hello cabal"


class Injection a b where
  inject :: a -> b

instance Injection a a where
  inject = id


try' :: forall m e1 e2 a. (MonadIO m, Exception e1, MonadError e2 m, Injection e1 e2) => IO a -> m a
try' x = do
  r <- liftIO (try x)
  case r of
    Left (e :: e1) -> throwError $ (inject e :: e2)
    Right a -> pure a
 

Я получаю следующую ошибку:

 Main.hs:24:9: error:
    • Could not deduce (Exception e10)
      from the context: (MonadIO m, Exception e1, MonadError e2 m,
                         Injection e1 e2)
        bound by the type signature for:
                   try' :: forall (m :: * -> *) e1 e2 a.
                           (MonadIO m, Exception e1, MonadError e2 m, Injection e1 e2) =>
                           IO a -> m a
        at Main.hs:24:9-100
      The type variable ‘e10’ is ambiguous
    • In the ambiguity check for ‘try'’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      In the type signature:
        try' :: forall m e1 e2 a.
                (MonadIO m, Exception e1, MonadError e2 m, Injection e1 e2) =>
                IO a -> m a
   |
24 | try' :: forall m e1 e2 a. (MonadIO m, Exception e1, MonadError e2 m, Injection e1 e2) => IO a -> m a
   |  
 

Как я могу написать try' функцию?

Комментарии:

1. Я подозреваю , что когда вы пишете Left (e :: e1) , это связывает новую переменную типа с именем e1 , не связанную с существующей в области видимости. Left e -> throwError $ (inject (e :: e1) :: e2) Может быть , попробовать?

2. …но тебе будет очень больно, даже если ты начнешь try' работать. Например, вы не сможете написать какие-либо другие экземпляры для Injection . Что заставило вас захотеть этот дизайн? Может быть, мы можем предложить более идиоматичный способ.

3. @DanielWagner не ScopedTypeVariables должен делать e1 однозначных выводов в этом случае? Ваше предложение (inject (e :: e1) :: e2) приводит к той же ошибке. Этот Injection класс просто упрощен Control.Monad.Except.CoHas . Идея состоит в том, чтобы иметь возможность соединять исключения GHC и MonadError обработку ошибок с помощью сопоставления классов друг с другом. В принципе, это должно дать вам также возможность построить какую-то иерархию исключений.

4. Блин. Я плохо соображал, когда писал свои предыдущие два комментария, оба просто неверны. Мои извинения.

Ответ №1:

Взгляните на этот тип:

 try' :: forall m e1 e2 a.
    (MonadIO m, Exception e1, MonadError e2 m, Injection e1 e2) =>
    IO a -> m a
 

Когда вы переходите к использованию try' , абсолютный максимум информации, которую можно предоставить механизму вывода типов по способу его использования, — это указать a и m -потому e1 что и e2 не отображаются в тексте типа. В классе есть функциональная зависимость MonadError , которая говорит, что знание m также исправляет e2 . Но e1 остается плавающим-может быть много типов , которые являются экземплярами Exception и могут быть введены e2 , и компилятор не знает, какой из них вы хотите!

В наши дни с TypeApplications помощью вы можете явно указывать аргументы типа. Если вы согласны с тем, чтобы все пользователи явно указывали свой выбор типов для e1 сайтов вызовов, вы можете применить исправление, предложенное в ошибке: включите AllowAmbiguousTypes и уходите.