Использование Eithers с Scala "для" синтаксиса

Насколько я понимаю, Scala синтаксис "for" чрезвычайно похож на монадический синтаксис "do" в Haskell. В Scala синтаксис "for" часто используется для Listс и Options. Я хотел бы использовать его с Eithers, но необходимые методы отсутствуют в импорте по умолчанию.

for {
  foo <- Right(1)
  bar <- Left("nope")
} yield (foo + bar)

// expected result: Left("nope")
// instead I get "error: value flatMap is not a member..."

Доступна ли эта функциональность через какой-то импорт?

Есть небольшая заминка:

for {
  foo <- Right(1)
  if foo > 3
} yield foo
// expected result: Left(???)

Для списка это было бы List(), За Option, это было бы None, Стандартные библиотеки Scala предоставляют решение этой проблемы? (Или возможно scalaz?) Как? Предположим, я хотел предоставить свой собственный "экземпляр монады" для Either, как я мог это сделать?

3 ответа

Решение

Это не работает в Scala 2.11 и ранее, потому что Either это не монада. Хотя есть разговоры о правильном смещении, вы не можете использовать это для понимания: вы должны получить LeftProject или же RightProjectionкак показано ниже:

for {
  foo <- Right[String,Int](1).right
  bar <- Left[String,Int]("nope").right
} yield (foo + bar)

Что возвращает Left("nope"), Кстати.

На Скалазе вы бы заменили Either с Validation, Интересный факт: EitherПервоначальный автор - Тони Моррис, один из авторов Скалаза. Он хотел сделать Either предвзятый, но был убежден коллегой в обратном.

Начиная с версии Scala 2.12, Either сейчас правы

Из документации:

Так как Either определяет методы map и flatMap, его также можно использовать для понимания:

val right1: Right[Double, Int] = Right(1)
val right2                     = Right(2)
val right3                     = Right(3)
val left23: Left[Double, Int]  = Left(23.0)
val left42                     = Left(42.0)

for (
  a <- right1;
  b <- right2;
  c <- right3
) yield a + b + c // Right(6)

for (
  a <- right1;
  b <- right2;
  c <- left23
) yield a + b + c // Left(23.0)

for (
  a <- right1;
  b <- left23;
  c <- right2
) yield a + b + c // Left(23.0)

// It is advisable to provide the type of the “missing” value (especially the right value for `Left`)
// as otherwise that type might be infered as `Nothing` without context:
for (
  a <- left23;
  b <- right1;
  c <- left42  // type at this position: Either[Double, Nothing]
) yield a + b + c
//            ^
// error: ambiguous reference to overloaded definition,
// both method + in class Int of type (x: Char)Int
// and  method + in class Int of type (x: Byte)Int
// match argument types (Nothing)

Доступна ли эта функциональность через какой-то импорт?

Да, но через сторонний импорт: Scalaz предоставляет Monad экземпляр для Either,

import scalaz._, Scalaz._

scala> for {
     |   foo <- 1.right[String]
     |   bar <- "nope".left[Int]
     | } yield (foo.toString + bar)
res39: Either[String,java.lang.String] = Left(nope)

Сейчас if Страж не монадическая операция. Поэтому, если вы пытаетесь использовать if -guard, это приводит к ошибке компилятора, как и ожидалось.

scala> for {
     |   foo <- 1.right[String]
     |   if foo > 3
     | } yield foo
<console>:18: error: value withFilter is not a member of Either[String,Int]
                foo <- 1.right[String]
                              ^

Удобные методы, использованные выше - .right а также .left - также из Скалаза.

Редактировать:

Я пропустил этот твой вопрос.

Предположим, я хотел предоставить свой собственный "экземпляр монады" для Either, как я мог это сделать?

Scala for Понимания просто переводятся на .map, .flatMap, .withFilter, а также .filter.foreach звонки на вовлеченных объектах. (Вы можете найти полную схему перевода здесь.) Так что, если у некоторого класса нет необходимых методов, вы можете добавить их в класс, используя неявные преобразования.

Свежая сессия REPL ниже.

scala> implicit def eitherW[A, B](e: Either[A, B]) = new {
     |   def map[B1](f: B => B1) = e.right map f
     |   def flatMap[B1](f: B => Either[A, B1]) = e.right flatMap f
     | }
eitherW: [A, B](e: Either[A,B])java.lang.Object{def map[B1](f: B => B1): Product 
with Either[A,B1] with Serializable; def flatMap[B1](f: B => Either[A,B1]):
Either[A,B1]}

scala> for {
     |   foo <- Right(1): Either[String, Int]
     |   bar <- Left("nope") : Either[String, Int]
     | } yield (foo.toString + bar)
res0: Either[String,java.lang.String] = Left(nope)
Другие вопросы по тегам