当 Haskell 尝试解析 MonadError e m 时,默认情况下,它会同时搜索 e 和 m 参数,以查找恰好有实例的任何对。如果我们没有 e 出现在约束本身之外的类型签名中的任何位置,这尤其困难
unitError :: MonadError e m => m ()
unitError = return ()
函数依赖表明,一旦我们解决了m,就只能有一个e 有效。这让上面的片段可以编译,因为它向 Haskell 保证那里有足够的信息让它有一个明确的类型。
如果没有函数依赖,Haskell 会抱怨 unitError 是模棱两可的,因为它可能对任何类型 e 都有效,而且我们无法知道该类型是什么——信息不知何故蒸发成稀薄的空气。
对于MonadError,函数依赖通常意味着 monad 本身是由错误类型参数化的。例如,这里有一个实例
instance MonadError e (Either e) where
thowError = Left
catchError (Left e) f = f e
catchError (Right a) _ = Right a
在e ~ e 和m ~ Either e 的位置,我们看到m 确实唯一标识了一个可能有效的e。
函数依赖也“几乎”等同于类型族。类型家庭有时更容易消化。例如,这是一个MonadError 类,TypeFamilies 样式
{-# LANGUAGE TypeFamilies #-}
class MonadError m where
type Err m
throwError :: Err m -> m a
catchError :: m a -> (Err m -> m a) -> m a
instance MonadError (Either e) where
type Err (Either e) = e
throwError = Left
catchError (Left e) f = f e
catchError (Right a) _ = Right a
这里,Err 是一个类型函数,它将一个 m 带到它的特定错误类型 e 中,并且对于任何 m 是否有一个等于 Err m 的 e 的概念自然来自我们对函数的理解。