Skip to main content

Option

note

Also check out: Option

Construct Option​

.some for Some / none for None​

Use .some and none[A] from cats instead of Some and None.

import cats.syntax.all._
1.some
// res1: Option[Int] = Some(value = 1)

vs

Some(1)
// res2: Some[Int] = Some(value = 1)

none[Int]
// res3: Option[Int] = None

vs

None
// res4: None.type = None

Conditional Construction​

Conditionally construct Option.

val num1 = 1
// num1: Int = 1
Option.when(num1 > 0)(num1)
// res6: Option[Int] = Some(value = 1)

val num2 = 0
// num2: Int = 0
Option.when(num2 > 0)(num2)
// res7: Option[Int] = None

val num3 = -1
// num3: Int = -1
Option.when(num3 > 0)(num3)
// res8: Option[Int] = None

Change Value​

map​

Option.map is used to change value inside Option.

info

NOTE: Each map returns a new Option instance.

val maybeNumber1 = Option(1)
// maybeNumber1: Option[Int] = Some(value = 1)

maybeNumber1.map(n => n * 2)
// res10: Option[Int] = Some(value = 2)

maybeNumber1.map(n => n + 10)
// res11: Option[Int] = Some(value = 11)

maybeNumber1.map(n => n - 1)
// res12: Option[Int] = Some(value = 0)
val maybeNumber2: Option[Int] = Option.empty[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.map(n => n * 2)
// res14: Option[Int] = None

maybeNumber2.map(n => n + 10)
// res15: Option[Int] = None

maybeNumber2.map(n => n - 1)
// res16: Option[Int] = None
val maybeNumber1 = Option(1)
// maybeNumber1: Option[Int] = Some(value = 1)

maybeNumber1.map(n => s"Number is ${n.toString}")
// res18: Option[String] = Some(value = "Number is 1")
val maybeNumber2: Option[Int] = Option.empty[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.map(n => s"Number is ${n.toString}")
// res20: Option[String] = None

flatMap​

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

  • Examples of nested Options
// for value.some to create Option[A] = Some(value) and none[A] to create Option[A] = None
import cats.syntax.all._
val maybeNumber = 1.some
// maybeNumber: Option[Int] = Some(value = 1)

maybeNumber.map(n => (n * 2).some)
// res22: Option[Option[Int]] = Some(value = Some(value = 2))

maybeNumber.map(n => (n + 10).some)
// res23: Option[Option[Int]] = Some(value = Some(value = 11))

maybeNumber.map(n => (n - 1).some)
// res24: Option[Option[Int]] = Some(value = Some(value = 0))
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.map(n => (n * 2).some)
// res25: Option[Option[Int]] = None

maybeNumber2.map(n => (n + 10).some)
// res26: Option[Option[Int]] = None

maybeNumber2.map(n => (n - 1).some)
// res27: Option[Option[Int]] = None
  • With flatMap
// for value.some to create Option[A] = Some(value) and none[A] to create Option[A] = None
import cats.syntax.all._
val maybeNumber = 1.some
// maybeNumber: Option[Int] = Some(value = 1)

maybeNumber.flatMap(n => (n * 2).some)
// res29: Option[Int] = Some(value = 2)

maybeNumber.flatMap(n => (n + 10).some)
// res30: Option[Int] = Some(value = 11)

maybeNumber.flatMap(n => (n - 1).some)
// res31: Option[Int] = Some(value = 0)
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.flatMap(n => (n * 2).some)
// res32: Option[Int] = None

maybeNumber2.flatMap(n => (n + 10).some)
// res33: Option[Int] = None

maybeNumber2.flatMap(n => (n - 1).some)
// res34: Option[Int] = None
  • More examples
// for value.some to create Option[A] = Some(value) and none[A] to create Option[A] = None
import cats.syntax.all._
val maybeNumber1 = 1.some
// maybeNumber1: Option[Int] = Some(value = 1)

maybeNumber1.flatMap { n =>
if (n < 0) none
else (n * 2).some
}
// res36: Option[Int] = Some(value = 2)
val maybeNumber2 = -1.some
// maybeNumber2: Option[Int] = Some(value = -1)
maybeNumber2.flatMap { n =>
if (n < 0) none
else (n * 2).some
}
// res37: Option[Int] = None
val maybeNumber3 = none[Int]
// maybeNumber3: Option[Int] = None
maybeNumber3.flatMap { n =>
if (n < 0) none
else (n * 2).some
}
// res38: Option[Int] = None

Get Value​

You may think about using Option.get, but it's a bad practice which can cause a runtime exception. There are other safe ways to get the value out of Option.

Pattern Matching​

Pattern matching is the most common and easy to understand way to get the value from Option.

import cats.syntax.all._
val maybeNumber1 = 999.some
// maybeNumber1: Option[Int] = Some(value = 999)

val number1 = maybeNumber1 match {
case Some(number) => number
case None => 0
}
// number1: Int = 999

println(s"number1 is ${number1.toString}.")
// number1 is 999.
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

val number2 = maybeNumber2 match {
case Some(number) => number
case None => 0
}
// number2: Int = 0

println(s"number2 is ${number2.toString}.")
// number2 is 0.

import cats.syntax.all._
val maybeNumber = 999.some
// maybeNumber: Option[Int] = Some(value = 999)

maybeNumber match {
case Some(number) => println(s"The number is ${number.toString}.")
case None => println("There is no number found.")
}
// The number is 999.
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2 match {
case Some(number) => println(s"The number is ${number.toString}.")
case None => println("There is no number found.")
}
// There is no number found.

getOrElse​

Alternatively, you can use Option.getOrElse.

import cats.syntax.all._
val maybeNumber1 = 999.some
// maybeNumber1: Option[Int] = Some(value = 999)

val number1 = maybeNumber1.getOrElse(0)
// number1: Int = 999

println(s"number1 is ${number1.toString}.")
// number1 is 999.
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

val number2 = maybeNumber2.getOrElse(0)
// number2: Int = 0

println(s"number2 is ${number2.toString}.")
// number2 is 0.

fold (Catamorphism)​

If you want to change the value when it's Some and get the value, you can use fold. It's the same as map then getOrElse.

import cats.syntax.all._
val maybeNumber1 = 999.some
// maybeNumber1: Option[Int] = Some(value = 999)

val result1 = maybeNumber1.fold("No number2 is found.")(num => s"number1 is ${num.toString}.")
// result1: String = "number1 is 999."

println(s"Result1: $result1")
// Result1: number1 is 999.

which is equivalent to

import cats.syntax.all._
val maybeNumber1 = 999.some
// maybeNumber1: Option[Int] = Some(value = 999)

val result1 = maybeNumber1
.map(num => s"number1 is ${num.toString}.")
.getOrElse("No number2 is found.")
// result1: String = "number1 is 999."

println(s"Result1: $result1")
// Result1: number1 is 999.

import cats.syntax.all._
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

val result2 = maybeNumber2.fold("No number2 is found.")(num => s"number2 is ${num.toString}.")
// result2: String = "No number2 is found."

println(s"Result2: $result2")
// Result2: No number2 is found.

is equivalent to

import cats.syntax.all._
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

val result2 = maybeNumber2
.map(num => s"number2 is ${num.toString}.")
.getOrElse("No number2 is found.")
// result2: String = "No number2 is found."

println(s"Result2: $result2")
// Result2: No number2 is found.

Given Option[A], Option[A].filter takes a function A => Boolean. The function takes the value of type A in the Option[A].

  • When Option[A] is Some(a: A),
    • If the function returns true, filter returns the Some(a: A) again.
    • If the function returns false, filter returns None.
  • When Option[A] is None
    • It always returns None.

filter​

import cats.syntax.all._
val maybeNumber1 = 1.some
// maybeNumber1: Option[Int] = Some(value = 1)

maybeNumber1.filter(n => n >= 0)
// res57: Option[Int] = Some(value = 1)

maybeNumber1.filter(n => n >= 1)
// res58: Option[Int] = Some(value = 1)

maybeNumber1.filter(n => n > 1)
// res59: Option[Int] = None
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.filter(n => n >= 0)
// res60: Option[Int] = None

maybeNumber2.filter(n => n >= 1)
// res61: Option[Int] = None

maybeNumber2.filter(n => n > 1)
// res62: Option[Int] = None

exists​

import cats.syntax.all._
val maybeNumber1 = 1.some
// maybeNumber1: Option[Int] = Some(value = 1)

maybeNumber1.exists(n => n >= 0)
// res64: Boolean = true

maybeNumber1.exists(n => n >= 1)
// res65: Boolean = true

maybeNumber1.exists(n => n > 1)
// res66: Boolean = false
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.exists(n => n >= 0)
// res67: Boolean = false

maybeNumber2.exists(n => n >= 1)
// res68: Boolean = false

maybeNumber2.exists(n => n > 1)
// res69: Boolean = false

contains​

import cats.syntax.all._
val maybeNumber1 = 1.some
// maybeNumber1: Option[Int] = Some(value = 1)

maybeNumber1.contains(0)
// res71: Boolean = false

maybeNumber1.contains(1)
// res72: Boolean = true

maybeNumber1.contains(2)
// res73: Boolean = false
val maybeNumber2 = none[Int]
// maybeNumber2: Option[Int] = None

maybeNumber2.contains(0)
// res74: Boolean = false

maybeNumber2.contains(1)
// res75: Boolean = false

maybeNumber2.contains(2)
// res76: Boolean = false

collect​

If you want to retain the value only if it's what you're looking for, or optionally transform the found value as if using map, you can use collect.

collect takes a PartialFunction and any non-matched value will be ignored and Option becomes None.

val maybeNum1 = Option(0)
// maybeNum1: Option[Int] = Some(value = 0)
maybeNum1.collect {
case 0 => -999
case n if n > 0 && n < 10 => n
}
// res78: Option[Int] = Some(value = -999)

val maybeNum2 = Option(1)
// maybeNum2: Option[Int] = Some(value = 1)
maybeNum2.collect {
case 0 => -999
case n if n > 0 && n < 10 => n
}
// res79: Option[Int] = Some(value = 1)

val maybeNum4 = Option(9)
// maybeNum4: Option[Int] = Some(value = 9)
maybeNum4.collect {
case 0 => -999
case n if n > 0 && n < 10 => n
}
// res80: Option[Int] = Some(value = 9)

val maybeNum3 = Option(10)
// maybeNum3: Option[Int] = Some(value = 10)
maybeNum3.collect {
case 0 => -999
case n if n > 0 && n < 10 => n
}
// res81: Option[Int] = None

val maybeNum5 = Option(-1)
// maybeNum5: Option[Int] = Some(value = -1)
maybeNum5.collect {
case 0 => -999
case n if n > 0 && n < 10 => n
}
// res82: Option[Int] = None

val maybeNum6 = Option.empty[Int]
// maybeNum6: Option[Int] = None
maybeNum6.collect {
case 0 => -999
case n if n > 0 && n < 10 => n
}
// res83: Option[Int] = None