Использование Eithers с Scala "для" синтаксиса
Насколько я понимаю, Scala синтаксис "for" чрезвычайно похож на монадический синтаксис "do" в Haskell. В Scala синтаксис "for" часто используется для List
с и Option
s. Я хотел бы использовать его с Either
s, но необходимые методы отсутствуют в импорте по умолчанию.
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)