半组类型类(任意),组合稍有改变

Flo*_*erl 4 functional-programming scala semigroup scala-cats

使用cats.Semigroup可以这样写:

import cats.Semigroup
import cats.implicits._

val l1: String Either Int = Left("error")
val r1: String Either Int = Right(1)
val r2: String Either Int = Right(2)

l1 |+| r1 // Left("error")
r1 |+| r2 // Right(3)
Run Code Online (Sandbox Code Playgroud)

我希望有一个同样惯用的运算符(类似combine),其工作方式如下:

  • 如果Right我的计算中至少有一个,则返回一个Right
  • 如果只有 Left,则返回一个Left

例如:

Right(1) |+| Right(2) // Right(3) 
Right(1) |+| Left("2") // Right(1) 
Left("1") |+| Left("2") // Left("12") // in my particular case the wrapped value here does not really matter (could also be e.g. Left("1") or Left("2")), but I guess Left("12") would be the must logical result
Run Code Online (Sandbox Code Playgroud)

是否已经在例如cats on中定义了类似的内容Either

Tra*_*own 10

还有一堆合法半群的实例Either,以及哪些应该被包含在了猫的问题有些 争论。在这方面,Cats,Scalaz和Haskell都做出了不同的选择,而您所描述的实例(翻转但左右合并)与所有这三个实例都不相同,因此我没有特定的名称我知道,而Cats并未以任何名称或任何形式提供它。

当然,这本身并不是问题,因为正如我们将在下面看到的那样,很容易验证此实例是否合法,但是您应该注意一个潜在的问题。您并没有真正解释您想要的语义,但是,如果您想将其提升为a Monoid,那么Right当您同时拥有a Left和a 时都选择了the Right意味着您必须将0设为Left。如果您将权利视为成功而将权利视为错误,而在合并值时可以忽略的错误,这可能会有些奇怪。

Semigroup但是,您不是在询问问题,Monoid所以让我们暂时将其忽略,并说明这是合法的。首先定义:

import cats.kernel.Semigroup

implicit def eitherSemigroup[A, B](implicit
  A: Semigroup[A],
  B: Semigroup[B]
): Semigroup[Either[A, B]] = Semigroup.instance {
  case (Right(x), Right(y)) => Right(B.combine(x, y))
  case (r @ Right(_), Left(_)) => r
  case (Left(_), r @ Right(_)) => r
  case (Left(x), Left(y)) => Left(A.combine(x, y))
}
Run Code Online (Sandbox Code Playgroud)

然后检查部分:

import cats.instances.int._
import cats.instances.string._
import cats.kernel.instances.either.catsStdEqForEither
import cats.kernel.laws.discipline.SemigroupTests
import org.scalacheck.Test.Parameters

SemigroupTests(eitherSemigroup[String, Int]).semigroup.all.check(Parameters.default)
Run Code Online (Sandbox Code Playgroud)

是的,很好:

+ semigroup.associative: OK, passed 100 tests.
+ semigroup.combineAllOption: OK, passed 100 tests.
+ semigroup.repeat1: OK, passed 100 tests.
+ semigroup.repeat2: OK, passed 100 tests.
Run Code Online (Sandbox Code Playgroud)

就我个人而言,如果我想要这样的东西,我可能会使用包装器来避免混淆我的代码的未来读者(包括我自己),但是鉴于没人真正知道semigroup Either 该做什么,我不认为使用自定义实例是与标准库中的大多数其他类型一样,这是一个大问题。