Класс типов полугрупп (Либо) со слегка измененным объединением - PullRequest
4 голосов
/ 14 марта 2019

Используя 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)

Я хотел бы иметь такой же идиоматический оператор (подобный комбинату), который работает так:

  • если в моих вычислениях есть (по крайней мере) один 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

Есть ли что-то подобное, например, уже определенное, например, кошки на Either?

1 Ответ

10 голосов
/ 14 марта 2019

Существует множество законных полугрупповых случаев для Either, и какой из них должен быть включен в Cats, был вопросом некоторых дебатов .Cats, Scalaz и Haskell делают разные выборы в этом отношении, и описываемый вами случай (перевернутый, но с левым и правым объединением) отличается от всех трех, у него нет конкретного имени, которое я 'Я знаю, и он не предоставляется ни под каким именем, ни в какой-либо форме компанией Cats.

Само по себе это не проблема, поскольку, как мы увидим ниже, довольно легко проверить, что этот экземплярзаконно, но есть одна потенциальная проблема, о которой вы должны знать.Вы на самом деле не объясняете свою предполагаемую семантику, но если вы когда-нибудь захотите повысить ее до Monoid, тот факт, что вы выбираете Right, когда у вас есть и Left, и Right, означает, что ваш нольдолжно быть 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))
}

А потом проверяющая часть:

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)

И да, все нормально:

+ semigroup.associative: OK, passed 100 tests.
+ semigroup.combineAllOption: OK, passed 100 tests.
+ semigroup.repeat1: OK, passed 100 tests.
+ semigroup.repeat2: OK, passed 100 tests.

Лично, если бы я хотел что-то вродевероятно, я бы использовал оболочку, чтобы не запутать будущих читателей моего кода (включая меня), но, учитывая, что никто на самом деле не знает, что должна делать полугруппа Either , я не думаю, что использоватьПользовательский экземпляр такой же большой проблемы, как и для большинства других типов из стандартной библиотеки.

...