Использование Eithers с синтаксисом Scala "для"
Как я понимаю, синтаксис Scala "для" очень похож на синтаксис синтаксиса Haskell monadic. В Scala синтаксис для синтаксиса часто используется для 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
?) Как? Предположим, я хотел предоставить свой собственный "экземпляр монады" для Либо, как я мог это сделать?
Ответы
Ответ 1
Он не работает в scala 2.11 и ранее, потому что Either
не является монадой. Хотя там говорят о правильном смещении, вы не можете использовать его для понимания: вы должны получить LeftProject
или RightProjection
, как показано ниже:
for {
foo <- Right[String,Int](1).right
bar <- Left[String,Int]("nope").right
} yield (foo + bar)
Это возвращает Left("nope")
, кстати.
В Scalaz вы замените Either
на Validation
. Интересный факт: Either
оригинальным автором является Тони Моррис, один из авторов Scalaz. Он хотел сделать Either
правым предвзятым, но был убежден другим коллегой.
Ответ 2
Доступна ли эта функциональность через некоторый импорт?
Да, но через импорт сторонних поставщиков: 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
-guard не является монадической операцией. Поэтому, если вы попытаетесь использовать 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
- также из Scalaz.
Edit:
Я пропустил этот ваш вопрос.
Предположим, я хотел предоставить свой собственный "экземпляр монады" для Либо, как я мог это сделать?
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)
Ответ 3
Как и в случае с Scala 2.12, Either
теперь правое предвзятое
Из документация:
Как и для определения карты методов и 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)