Either
Either
Right-Biased Either
Either
in Scala prior to 2.12 is not right-biased meaning that you have to call Either.right
all the time if you want to use it with for-comprehension
.
e.g.) Before 2.12
for {
b <- methodReturningEither(a).right
c <- anotherReturningEither(b).right
} yield c
If you use just-fp
, it becomes
import just.fp.syntax._
for {
b <- methodReturningEither(a)
c <- anotherReturningEither(b)
} yield c
Of course, you don't need to do it if you use Scala 2.12 or higher.
Either Constructors
In normal ways, if you want to create Left
or Right
, you just use the apply
methods of their companion objects (i.e. Left()
Right()
) A problem with this is that what these return is not Either
but its data, Left
or Right
.
You also need to specify not only type parameter for Left
but also the one for Right
when creating Right
.
e.g.) Without type parameters,
Right(1)
// res0: Right[Nothing, Int] = Right(value = 1)
You don't want to have Nothing
there. So do it with type parameters,
Right[String, Int](1)
// res1: Right[String, Int] = Right(value = 1)
So it becomes unnecessarily verbose. Right should be inferred as the compiler knows it already yet to specify the left one, you have to put both left and right parameters.
Left
, of course, has the same problem.
Left("error")
// res2: Left[String, Nothing] = Left(value = "error")
Left[String, Int]("error")
// res3: Left[String, Int] = Left(value = "error")
Now with just-fp
, it's simpler. You can use use left
and right
constructors as extension methods to the actual data values with only missing type info specified.
e.g.)
1.right[String] // Now you only need to specify
// res4: Either[String, Int] = Right(value = 1)
For Left
,
"error".left[Int]
// res5: Either[String, Int] = Left(value = "error")
leftMap
and leftFlatMap
So if you Scala 2.12 or higher or just-fp
with the older Scala, Either
is right-biassed. Then what about the Left
case? Can I ever use Left
for something useful like transforming the Left
value to something else?
For that, just-fp
has added leftMap
and leftFlatMap
to Either
.
e.g.)
import just.fp.syntax._
final case class ComputeError(error: String)
sealed trait AppError
object AppError {
final case class InvalidNumberError(error: String) extends AppError
final case class ComputationError(computeError: ComputeError) extends AppError
def invalidNumberError(error: String): AppError =
InvalidNumberError(error)
def fromComputeError(computeError: ComputeError): AppError =
ComputationError(computeError)
}
def f1(n: Int): Either[String, Int] =
if (n < 0)
Left(s"The number must be non-negative integer - n: $n")
else
Right(n)
def f2(x: Int, y: Int): Either[ComputeError, Int] = {
val z = x + y
if (x >= 0 && y >= 0 && z < 0)
Left(ComputeError(s"Numbers are too big - x: $x, y: $y"))
else
Right(x + y)
}
for {
b <- f1(10).leftMap(AppError.invalidNumberError)
c <- f2(123, b).leftMap(AppError.fromComputeError)
} yield c
// res7: Either[AppError, Int] = Right(value = 133)
for {
b <- f1(-1).leftMap(AppError.invalidNumberError)
c <- f2(123, b).leftMap(AppError.fromComputeError)
} yield c
// res8: Either[AppError, Int] = Left(
// value = InvalidNumberError(
// error = "The number must be non-negative integer - n: -1"
// )
// )
for {
b <- f1(Int.MaxValue).leftMap(AppError.invalidNumberError)
c <- f2(1, b).leftMap(AppError.fromComputeError)
} yield c
// res9: Either[AppError, Int] = Left(
// value = ComputationError(
// computeError = ComputeError(
// error = "Numbers are too big - x: 1, y: 2147483647"
// )
// )
// )