Monad 结果类型在 `fail` 时不产生 `Either String`

Monad result type does not produce `Either String` on `fail`

给定以下函数,它产生一个包装到 Monad 中的结果:

ab :: (Monad m) => Char -> m Bool
ab 'a' = return True
ab 'b' = return False
ab _   = fail "say what?"

以下用途如我所料:

ab 'a' :: [Bool]      -- results in [True]
ab 'c' :: [Bool]      -- results in []
ab 'b' :: Maybe Bool  -- results in Just b
ab 'c' :: Maybe Bool  -- results in Nothing
ab 'a' :: Either String Bool  -- results in Right True

但是,对于 Either Stringfail 实际上会产生异常,尽管我希望它是 Left 并带有错误消息:

> ab 'c' :: Either String Bool
*** Exception: say what?

这是为什么?有没有办法改变上面的代码(函数实现,或者它的应用方式)以便在失败的情况下产生 Left (当然保持它的通用性)。

过去,"standard" 库中没有 EitherMonad 实例。相反,这个实例是 defined in the mtl library. However, to make fail work as you desire we need a way to convert a string into whatever the type e is in Either e. This led to the Error class. Back in the day, the Monad instance in mtl had Error as a constraint on the Either instance of Monad. This was solely to make fail Do The Right Thing, and most people thought of fail as a bit of a wart on the Monad class. Unfortunately, it meant that you couldn't use any Monad operations on Either except when your "error" type was an instance of Error which wasn't typical or usually desirable. The orphan instance for Either also caused much trouble when a rival monad library, transformers,出现了。不过,最终,无论 "error" 类型如何,在 Either 上使用 monad 操作的有效性以及将 Monad 实例移动到 base 中占了上风,导致约束被删除。但是,现在没有可以为 fail.

定义的合理的东西

Why is it?

记住fail的类型:Monad m => String -> m a。现在,如果 Either 的 monad 实例仅为 Either String 定义,这将很容易:

instance Monad (Either String) where
    fail = Left
    ...

不过,实际实例比较笼统:

instance Monad (Either e) where
    ...

因此fail的类型也更通用,即使我们将它限制在这个特定的实例中:

-- No          v  restriction on e   v
fail :: forall e a. String -> Either e a
--      ^^^^^^^^^^
-- This is implicitly there every time you use a polymorphic function
-- (unless you start toying around with some extensions and move it further
--  to the right or into parentheses, see RankNTypes or similar extensions.)

并且由于 e 不限于 String,因此没有通用的方法将错误消息存储在 Left e 中。例如,下面的例子应该怎样 return?

example :: Either () () 
example = fail "Example"

即使您使用 Either String a,更通用的实例仍在使用。另一种方法是使用新类型或您自己的具有合适实例的 ADT:

data EitherString a  = ELeft String | ERight a

instance Monad EitherString where
    fail = ELeft
    ...


newtype EitherWrap a = Wrapped { unWrap :: Either String a }

instance Monad EitherWrap where
    fail = Wrapped . Left

请注意,a proposalMonad 类型类中的 fail 拆分为 MonadFail 类型类。

因为Either是析取,而不仅仅是success/error。常用于表示success/error(因为方便),但一般情况下不是

这就是数据构造函数被命名为 LeftRight 的原因。他们在代表价值的权利上是平等的。

例如,我有 Either Computation Result 或类似的东西是完全可以的。 Either Gold Money,等等

如果 fail 最终返回析取的分支之一,则可能不正确。

似乎可以使用 Data.String(IsString) class,即用于 OverloadedStrings 扩展的那个。

instance IsString a =>  MonadFail (Either a ) where 
  fail msg = Left $ fromString msg

但我想它不在标准库中是有原因的