Skip to main content

Either

note

Also check out: Either

Construct Either

Conditionally create either Right or Left.

val num1 = 1
// num1: Int = 1
Either.cond(num1 > 0, num1, "It should be a positive number")
// res0: Either[String, Int] = Right(value = 1)

val num2 = 0
// num2: Int = 0
Either.cond(num2 > 0, num2, "It should be a positive number")
// res1: Either[String, Int] = Left(value = "It should be a positive number")

val num3 = -1
// num3: Int = -1
Either.cond(num3 > 0, num3, "It should be a positive number")
// res2: Either[String, Int] = Left(value = "It should be a positive number")

Change Value

map

Either.map is used to change the value inside Either when it's Right as Either is right-biased.

info

NOTE: Each map returns a new Either instance.

// When value: B, value.asRight[A] to create Either[A, B] = Right(value) 
// When value2: A, value2.asLeft[B] to create Either[A, B] = Left(value2)
import cats.syntax.all._

val number = 1.asRight[String]
// number: Either[String, Int] = Right(value = 1)

number.map(n => n * 2)
// res4: Either[String, Int] = Right(value = 2)
number.map(n => n + 10)
// res5: Either[String, Int] = Right(value = 11)
number.map(n => n - 1)
// res6: Either[String, Int] = Right(value = 0)

val number2 = "Error".asLeft[Int]
// number2: Either[String, Int] = Left(value = "Error")

number2.map(n => n * 2)
// res7: Either[String, Int] = Left(value = "Error")
number2.map(n => n + 10)
// res8: Either[String, Int] = Left(value = "Error")
number2.map(n => n - 1)
// res9: Either[String, Int] = Left(value = "Error")
import cats.syntax.all._

val number = 1.asRight[String]
// number: Either[String, Int] = Right(value = 1)

number.map(n => s"Number is ${n.toString}")
// res11: Either[String, String] = Right(value = "Number is 1")

val number2 = "Error".asLeft[Int]
// number2: Either[String, Int] = Left(value = "Error")

number2.map(n => s"Number is ${n.toString}")
// res12: Either[String, String] = Left(value = "Error")

flatMap

If the return type of the function passed to Either.map is also Either, use Either.flatMap. Otherwise, you will end up having nested Eithers like Either[A, Either[B, C]]

  • Examples of nested Eithers
// When value: B, value.asRight[A] to create Either[A, B] = Right(value)
// When value2: A, value2.asLeft[B] to create Either[A, B] = Left(value2)
import cats.syntax.all._

val number = 1.asRight[String]
// number: Either[String, Int] = Right(value = 1)

number.map(n => (n * 2).asRight)
// res14: Either[String, Either[Nothing, Int]] = Right(
// value = Right(value = 2)
// )
number.map(n => (n + 10).asRight)
// res15: Either[String, Either[Nothing, Int]] = Right(
// value = Right(value = 11)
// )
number.map(n => (n - 1).asRight)
// res16: Either[String, Either[Nothing, Int]] = Right(
// value = Right(value = 0)
// )

val number2 = "Error".asLeft[Int]
// number2: Either[String, Int] = Left(value = "Error")

number2.map(n => (n * 2).asRight)
// res17: Either[String, Either[Nothing, Int]] = Left(value = "Error")
number2.map(n => (n + 10).asRight)
// res18: Either[String, Either[Nothing, Int]] = Left(value = "Error")
number2.map(n => (n - 1).asRight)
// res19: Either[String, Either[Nothing, Int]] = Left(value = "Error")
  • With flatMap
// When value: B, value.asRight[A] to create Either[A, B] = Right(value)
// When value2: A, value2.asLeft[B] to create Either[A, B] = Left(value2)
import cats.syntax.all._

val number = 1.asRight
// number: Either[Nothing, Int] = Right(value = 1)

number.flatMap(n => (n * 2).asRight)
// res21: Either[Nothing, Int] = Right(value = 2)
number.flatMap(n => (n + 10).asRight)
// res22: Either[Nothing, Int] = Right(value = 11)
number.flatMap(n => (n - 1).asRight)
// res23: Either[Nothing, Int] = Right(value = 0)

val number2 = "Error".asLeft[Int]
// number2: Either[String, Int] = Left(value = "Error")

number2.flatMap(n => (n * 2).asRight)
// res24: Either[String, Int] = Left(value = "Error")
number2.flatMap(n => (n + 10).asRight)
// res25: Either[String, Int] = Left(value = "Error")
number2.flatMap(n => (n - 1).asRight)
// res26: Either[String, Int] = Left(value = "Error")
  • More examples
// When value: B, value.asRight[A] to create Either[A, B] = Right(value)
// When value2: A, value2.asLeft[B] to create Either[A, B] = Left(value2)
import cats.syntax.all._

val number = 1.asRight
// number: Either[Nothing, Int] = Right(value = 1)

number.flatMap { n =>
if (n < 0) "n should not be a negative Int".asLeft
else (n * 2).asRight
}
// res28: Either[String, Int] = Right(value = 2)

val number2 = -1.asRight
// number2: Either[Nothing, Int] = Right(value = -1)
number2.flatMap { n =>
if (n < 0) "n should not be a negative Int".asLeft
else (n * 2).asRight
}
// res29: Either[String, Int] = Left(value = "n should not be a negative Int")

val number3 = "Error".asLeft[Int]
// number3: Either[String, Int] = Left(value = "Error")
number3.flatMap { n =>
if (n < 0) "n should not be a negative Int".asLeft
else (n * 2).asRight
}
// res30: Either[String, Int] = Left(value = "Error")

Get Value

There are few ways to get the value out of Either. Since Either can be either Right or Left, you need to provide ways to handle both Right and Left values.

Pattern Matching

import cats.syntax.all._
val errorOrNumber1 = 999.asRight[String]
// errorOrNumber1: Either[String, Int] = Right(value = 999)

errorOrNumber1 match {
case Right(n) => n
case Left(error) => 0
}
// res32: Int = 999
val errorOrNumber2 = "ERROR".asLeft[Int]
// errorOrNumber2: Either[String, Int] = Left(value = "ERROR")

errorOrNumber2 match {
case Right(n) => n
case Left(error) => 0
}
// res33: Int = 0

fold (Catamorphism)

import cats.syntax.all._
val errorOrNumber1 = 999.asRight[String]
// errorOrNumber1: Either[String, Int] = Right(value = 999)

errorOrNumber1.fold(
error => 0,
n => n
)
// res35: Int = 999
val errorOrNumber2 = "ERROR".asLeft[Int]
// errorOrNumber2: Either[String, Int] = Left(value = "ERROR")

errorOrNumber2.fold(
error => 0,
n => n
)
// res36: Int = 0
val errorOrNumber3 = 999.asRight[String]
// errorOrNumber3: Either[String, Int] = Right(value = 999)

errorOrNumber3.fold(
error => 0,
n => n + 1
)
// res37: Int = 1000
val errorOrNumber4 = "ERROR".asLeft[Int]
// errorOrNumber4: Either[String, Int] = Left(value = "ERROR")

errorOrNumber4.fold(
error => 0,
n => n + 1
)
// res38: Int = 0
val errorOrNumber5 = 999.asRight[String]
// errorOrNumber5: Either[String, Int] = Right(value = 999)

errorOrNumber5.fold(
error => println(s"ERROR: $error"),
n => println(s"Number=$n")
)
// Number=999
val errorOrNumber6 = "ERROR".asLeft[Int]
// errorOrNumber6: Either[String, Int] = Left(value = "ERROR")

errorOrNumber6.fold(
error => println(s"ERROR: $error"),
n => println(s"Number=$n")
)
// ERROR: ERROR

getOrElse

import cats.syntax.all._
val errorOrNumber1 = 999.asRight[String]
// errorOrNumber1: Either[String, Int] = Right(value = 999)

errorOrNumber1.getOrElse(0)
// res42: Int = 999
val errorOrNumber2 = "ERROR".asLeft[Int]
// errorOrNumber2: Either[String, Int] = Left(value = "ERROR")

errorOrNumber2.getOrElse(0)
// res43: Int = 0

filterOrElse

note

Either is Right-biased so filterOrElse works only for Right. When it's Left nothing happens with filterOrElse.

import cats.syntax.all._
val errorOrNumber1 = 1.asRight[String]
// errorOrNumber1: Either[String, Int] = Right(value = 1)

errorOrNumber1.filterOrElse(n => n >= 0, -999)
// res45: Either[Any, Int] = Right(value = 1)

errorOrNumber1.filterOrElse(n => n >= 1, -999)
// res46: Either[Any, Int] = Right(value = 1)

errorOrNumber1.filterOrElse(n => n > 1, -999)
// res47: Either[Any, Int] = Left(value = -999)
val errorOrNumber2 = "No number found".asLeft[Int]
// errorOrNumber2: Either[String, Int] = Left(value = "No number found")

errorOrNumber2.filterOrElse(n => n >= 0, -999)
// res48: Either[Any, Int] = Left(value = "No number found")

errorOrNumber2.filterOrElse(n => n >= 1, -999)
// res49: Either[Any, Int] = Left(value = "No number found")

errorOrNumber2.filterOrElse(n => n > 1, -999)
// res50: Either[Any, Int] = Left(value = "No number found")

exists

import cats.syntax.all._
val errorOrNumber1 = 1.asRight
// errorOrNumber1: Either[Nothing, Int] = Right(value = 1)

errorOrNumber1.exists(n => n >= 0)
// res52: Boolean = true

errorOrNumber1.exists(n => n >= 1)
// res53: Boolean = true

errorOrNumber1.exists(n => n > 1)
// res54: Boolean = false
val errorOrNumber2 = "No number found".asLeft[Int]
// errorOrNumber2: Either[String, Int] = Left(value = "No number found")

errorOrNumber2.exists(n => n >= 0)
// res55: Boolean = false

errorOrNumber2.exists(n => n >= 1)
// res56: Boolean = false

errorOrNumber2.exists(n => n > 1)
// res57: Boolean = false