LYAHFGG:
The
Either e a
type on the other hand, allows us to incorporate a context of possible failure to our values while also being able to attach values to the failure, so that they can describe what went wrong or provide some other useful info regarding the failure.
We know Either[A, B]
from the standard library, but Scalaz 7 implements its own Either
equivalent named \/
:
sealed trait \/[+A, +B] {
...
/** Return `true` if this disjunction is left. */
def isLeft: Boolean =
this match {
case -\/(_) => true
case \/-(_) => false
}
/** Return `true` if this disjunction is right. */
def isRight: Boolean =
this match {
case -\/(_) => false
case \/-(_) => true
}
...
/** Flip the left/right values in this disjunction. Alias for `unary_~` */
def swap: (B \/ A) =
this match {
case -\/(a) => \/-(a)
case \/-(b) => -\/(b)
}
/** Flip the left/right values in this disjunction. Alias for `swap` */
def unary_~ : (B \/ A) = swap
...
/** Return the right value of this disjunction or the given default if left. Alias for `|` */
def getOrElse[BB >: B](x: => BB): BB =
toOption getOrElse x
/** Return the right value of this disjunction or the given default if left. Alias for `getOrElse` */
def |[BB >: B](x: => BB): BB = getOrElse(x)
/** Return this if it is a right, otherwise, return the given value. Alias for `|||` */
def orElse[AA >: A, BB >: B](x: => AA \/ BB): AA \/ BB =
this match {
case -\/(_) => x
case \/-(_) => this
}
/** Return this if it is a right, otherwise, return the given value. Alias for `orElse` */
def |||[AA >: A, BB >: B](x: => AA \/ BB): AA \/ BB = orElse(x)
...
}
private case class -\/[+A](a: A) extends (A \/ Nothing)
private case class \/-[+B](b: B) extends (Nothing \/ B)
These values are created using right
and left
method injected to all data types via IdOps
:
scala> 1.right[String]
res12: scalaz.\/[String,Int] = \/-(1)
scala> "error".left[Int]
res13: scalaz.\/[String,Int] = -\/(error)
The Either
type in Scala standard library is not a monad on its own, which means it does not implement flatMap
method with or without Scalaz:
scala> Left[String, Int]("boom") flatMap { x => Right[String, Int](x + 1) }
<console>:8: error: value flatMap is not a member of scala.util.Left[String,Int]
Left[String, Int]("boom") flatMap { x => Right[String, Int](x + 1) }
^
You have to call right
method to turn it into RightProjection
:
scala> Left[String, Int]("boom").right flatMap { x => Right[String, Int](x + 1)}
res15: scala.util.Either[String,Int] = Left(boom)
This is silly since the point of having Either
is to report an error on the left. Scalaz’s \/
assumes that you’d mostly want right projection:
scala> "boom".left[Int] >>= { x => (x + 1).right }
res18: scalaz.Unapply[scalaz.Bind,scalaz.\/[String,Int]]{type M[X] = scalaz.\/[String,X]; type A = Int}#M[Int] = -\/(boom)
This is nice. Let’s try using it in for
syntax:
scala> for {
e1 <- "event 1 ok".right
e2 <- "event 2 failed!".left[String]
e3 <- "event 3 failed!".left[String]
} yield (e1 |+| e2 |+| e3)
res24: scalaz.\/[String,String] = -\/(event 2 failed!)
As you can see, the first failure rolls up as the final result. How do we get the value out of \/
? First there’s isRight
and isLeft
method to check which side we are on:
scala> "event 1 ok".right.isRight
res25: Boolean = true
scala> "event 1 ok".right.isLeft
res26: Boolean = false
For right side, we can use getOrElse
and its symbolic alias |
as follows:
scala> "event 1 ok".right | "something bad"
res27: String = event 1 ok
For left value, we can call swap
method or it’s symbolic alias unary_~
:
scala> ~"event 2 failed!".left[String] | "something good"
res28: String = event 2 failed!
We can use map
to modify the right side value:
scala> "event 1 ok".right map {_ + "!"}
res31: scalaz.\/[Nothing,String] = \/-(event 1 ok!)
To chain on the left side, there’s orElse
, which accepts => AA \/ BB
where [AA >: A, BB >: B]
. The symbolic alias for orElse
is |||
:
scala> "event 1 failed!".left ||| "retry event 1 ok".right
res32: scalaz.\/[String,String] = \/-(retry event 1 ok)