为什么 `MonadError` 有函数依赖?

Cli*_*ton 4 monads haskell

看来这可能有用:

data Err1 = Err1
data Err2 = Err2

f :: (MonadError Err1 m) => m Int
f = _

g :: (MonadError Err2 m) => m Char
g = _ 

h :: (MonadError Err1 m, MonadError Err2 m) => m (Int, Char)
h = (,) <$> f <*> g
Run Code Online (Sandbox Code Playgroud)

虽然上面的编译h是不可用的,因为一旦尝试为MonadError相同的对象定义两个实例m,该类的功能依赖性MonadError将导致错误。

我问的问题是为什么MonadError有这种功能依赖性?它似乎降低了它的灵活性,所以我认为有一些好处?能否给出一个代码示例,MonadError如果删除函数依赖性,使用哪种代码会更糟/不起作用?有些事情让我觉得我错过了一些关于如何MonadError使用的东西,因为函数依赖似乎妨碍了。

ama*_*loy 6

假设你的h函数工作正常:有一些 monad 具有 Err1 和 Err2 的 MonadError 实例。很好,throwError是该类的一个方法,因此throwError (e1 :: Err1)和throwError (e2 :: Err2)都是有效的。现在有人可能会这样写:

err1ToCode :: forall m. MonadError Err1 m => m Int
err1ToCode = foo `catchError` handle
  where handle :: Err1 -> m Int
        handle (Err1 errorCode) = pure errorCode
Run Code Online (Sandbox Code Playgroud)

假设err1ToCode从不抛出异常似乎是相当合理的:任何foo抛出的异常都会转换为其错误代码。这样的保证使得处理错误变得更加可预测。但是,如果foo也可能抛出其他未知类型的异常(例如 Err2),则err1ToCode尽管显然捕获了所有错误,但仍可能抛出错误。

所以我不认为从根本上不可能给一个类型提供多个 MonadError 实例,但在大多数情况下它会让事情变得不那么愉快:你永远不能假设你已经捕获并处理了所有错误,因为某些实例具有另一种错误类型可能潜伏在那里。

我也看不出它能带来什么特别的好处。您希望能够抛出多种类型的错误,这很公平 - 许多语言都有允许这样做的异常设施。但是您可以在不需要多个 MonadError 实例的情况下执行此操作。只需使用包含您想要抛出的所有错误的错误类型:

data KnownErrors = E1 Err1 | E2 Err2

f :: MonadError KnownErrors m => m Int
f = pure 1
g :: MonadError KnownErrors m => m Char
g = throwError (E2 (Err2 "broken"))
h :: MonadError KnownErrors m => m (Int, Char)
h = (,) <$> f <*> g
Run Code Online (Sandbox Code Playgroud)