是否有函数 transforms/maps Either's Left 和 Right cases 分别采用两个转换函数?

Is there a function that transforms/maps both Either's Left and Right cases taking two transformation functions respectively?

我没有在 Scala 或 Haskell 中找到可以 transform/map EitherLeftRight 两个转换函数的函数同时,即

类型的函数
(A => C, B => D) => Either[C, D]

对于 Scala 中的 Either[A, B],或者类型

(a -> c, b -> d) -> Either a b -> Either c d

在 Haskell 中。在 Scala 中,它相当于这样调用 fold

def mapLeftOrRight[A, B, C, D](e: Either[A, B], fa: A => C, fb: B => D): Either[C, D] =
  e.fold(a => Left(fa(a)), b => Right(fb(b)))

或者在Haskell中,它相当于这样调用either

mapLeftOrRight :: (a -> c) -> (b -> d) -> Either a b -> Either c d
mapLeftOrRight fa fb = either (Left . fa) (Right . fb)

库中有这样的函数吗?如果没有,我觉得这样的东西还是挺实用的,为什么语言设计者不选择放在那里呢?

不了解 Scala,但 Haskell 有一个类型签名搜索引擎。它没有给出你写的那个的结果,但这只是因为你采用了一个元组参数,而 Haskell 函数按照惯例是 curriedhttps://hoogle.haskell.org/?hoogle=(a -> c) -> (b -> d) -> Either a b -> Either c d 确实匹配,最明显的是:

<a href="https://hackage.haskell.org/package/either/docs/Data-Either-Combinators.html#v:mapBoth" rel="noreferrer">mapBoth</a> :: (a -> c) -> (b -> d) -> Either a b -> Either c d

...实际上,甚至 Google 也发现了这一点,因为类型变量恰好与您想象的完全一样。 (Hoogle 也找到它 if you write it (x -> y) -> (p -> q) -> Either x p -> Either y q。)

但实际上,正如 Martijn 所说,Either 的这种行为只是 bifunctor 的一个特例,实际上 Hoogle 也给了你更一般的形式,在 base 库中定义:

<a href="https://hackage.haskell.org/package/base/docs/Data-Bifunctor.html#v:bimap" rel="noreferrer">bimap</a> :: Bifunctor p => (a -> b) -> (c -> d) -> p a c -> p b d

TBH 我有点失望 Hoogle 本身并没有想出咖喱签名或交换参数。很确定它实际上曾经自动执行此操作,但在某些时候他们简化了算法,因为由于库数量巨大,它花费的时间和结果数量失控了。

您所说的行为是双函子行为,通常称为 bimap。在 Haskell 中,任何一个的双函子都可用:https://hackage.haskell.org/package/bifunctors-5/docs/Data-Bifunctor.html

除了你展示的折叠之外,scala 中的另一个实现是 either.map(fb).left.map(fa)

scala stdlib 中没有这样的方法,可能是因为发现它不够有用或不够基础。我可以在某种程度上与此相关:在一个操作中映射双方而不是单独映射每一方并不足够基本或有用以保证包含在我的 scala stdlib 中。不过,双函子在 Cats 中可用。

在Haskell中,该方法作为mapBoth存在于Either上,而BiFunctor在base.

猫提供Bifunctor,例如

import cats.implicits._

val e: Either[String, Int] = Right(41)
e.bimap(e => s"boom: $e", v => 1 + v)
// res0: Either[String,Int] = Right(42)

在 Haskell 中,您可以使用 Control.Arrow.(+++),它适用于任何 ArrowChoice:

(+++) :: (ArrowChoice arr) => arr a b -> arr c d -> arr (Either a c) (Either b d)
infixr 2 +++

专门针对函数箭头arr ~ (->),即:

(+++) :: (a -> b) -> (c -> d) -> Either a c -> Either b d

Hoogle 如果您搜索专门用于函数的类型,则不会找到 +++,但是您可以通过在您想要的签名中替换 -> 来找到这样的通用运算符类型变量:x a c -> x b d -> x (Either a b) (Either c d).

用法示例:

renderResults
  :: FilePath
  -> Int
  -> Int
  -> [Either String Int]
  -> [Either String String]
renderResults file line column
  = fmap ((prefix ++) +++ show)
  where
    prefix = concat [file, ":", show line, ":", show column, ": error: "]
renderResults "test" 12 34 [Right 1, Left "beans", Right 2, Left "bears"]
  ==
  [ Right "1"
  , Left "test:12:34: error: beans"
  , Right "2"
  , Left "test:12:34: error: bears"
  ]

还有一个相关运算符Control.Arrow.(|||),它没有用Either标记结果:

(|||) :: arr a c -> a b c -> arr (Either a b) c
infixr 2 |||

专攻(->)

(|||) :: (a -> c) -> (b -> c) -> Either a b -> c

示例:

assertRights :: [Either String a] -> [a]
assertRights = fmap (error ||| id)
sum $ assertRights [Right 1, Right 2]
  ==
  3

sum $ assertRights [Right 1, Left "oh no"]
  ==
  error "oh no"

(|||) 是 Haskell Preludeeither 函数的泛化,用于在 Either 上进行匹配。它用于ifcase箭头proc符号的脱糖。