将 Eithers 列表转换为包含列表的 Either

Converting a list of Eithers to an Either containing a list

我有一个函数,其中 return 是 Either 项的列表。如果它只包含 Right 个项目,我想 return 一个 Right 包含原始 Right 个项目中的值列表,但如果它包含任何 Left 项目 我想要一个 Left 包含原始 Left 项目中的值列表。所以如果我有: [Right "a", Right "b", Right "c"] 它应该变成: Right ["a", "b", "c"] 但如果我有: [Right "a", Left "b", Right "c", Left "d"] 它应该变成: Left ["b", "d"]

注意:Control.Monad 中的 sequence 函数不适用,因为它只保留第一个 Left 项。

我想到了这个:

eitherList :: [Either a1 a2] -> Either [a1] [a2]
eitherList l = if null lefts
               then Right rights
               else Left lefts
  where f1 (Left a) (al, ar) = (a : al, ar)
        f1 (Right a) (al, ar) = (al, a : ar)
        (lefts, rights) = foldr f1 ([], []) l

但看起来很笨拙。有没有更简单或更好的方法来做到这一点?我刚开始学习仿函数和单子,我想知道它们是否相关?

大部分繁重的工作可以由 Data.Either.partitionEithers 完成:

eitherList :: [Either a1 a2] -> Either [a1] [a2]
eitherList l = case (partitionEithers l) of
                  ([], rs) -> Right rs
                  (ls, _) -> Left ls

partitionEithers 的实现与您为 fold 编写的内容非常相似。)

The sequence function from Control.Monad isn't suitable because it only keeps the first Left item.

您的想法是正确的,只是缺少一些细节。有一个包,validation, that offers a type called Validation,它的形状就像 Either:

data Validation err a
  = Failure err
  | Success a

但值得注意的是,它的 Applicative 实例可以如您所愿地工作,将“错误”值附加在一起:

instance (Semigroup err) => Applicative (Validation err) where

  pure = Success

  Failure e1 <*> Failure e2 = Failure (e1 <> e2)
  Failure e1 <*> Success _  = Failure e1
  Success _  <*> Failure e2 = Failure e2
  Success f  <*> Success a  = Success (f a)

不像EitherValidation不能有Monad实例,所以你需要使用更通用的sequenceA(或traverse)而不是 sequence(或 mapM):

sequence  :: (Traversable t, Monad m)       => t (m a) -> m (t a)
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)

mapM      :: (Traversable t, Monad m)       => (a -> m b) -> t a -> m (t b)
traverse  :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

Data.Validation 模块导出各种用于与 Either 相互转换的函数。为了您的目的,一个不错的选择是 liftError:

liftError :: (b -> e) -> Either b a -> Validation e a

singleton :: a -> [a]
singleton = pure

liftError singleton :: Either e a -> Validation [e] a

全部都在这里:

import qualified Data.Validation as Validation

eitherList :: [Either e a] -> Either [e] [a]
eitherList = Validation.toEither . traverse (Validation.liftError pure)
-- Note: ‘traverse f’ = ‘sequenceA . fmap f’

并且您的示例按预期工作:

> example1 = [Right "a", Right "b", Right "c"]

> example2 = [Right "a", Left "b", Right "c", Left "d"]

> eitherList example1
Right ["a","b","c"]

> eitherList example2
Left ["b","d"]