Watch video

Video tutorial

Option

beginner

If you have worked with Java at all in the past, it is very likely that you have come across a NullPointerException at some time (other languages will throw similarly named errors in such a case). Usually this happens because some method returns null when you were not expecting it and thus not dealing with that possibility in your client code. A value of null is often abused to represent an absent optional value. Kotlin tries to solve the problem by getting rid of null values altogether and providing its own special syntax Null-safety machinery based on ?.

Arrow models the absence of values through the Option datatype similar to how Scala, Haskell and other FP languages handle optional values.

Option<A> is a container for an optional value of type A. If the value of type A is present, the Option<A> is an instance of Some<A>, containing the present value of type A. If the value is absent, the Option<A> is the object None.

import arrow.*
import arrow.core.*

val someValue: Option<String> = Some("I am wrapped in something")
someValue
// Some(I am wrapped in something)
val emptyValue: Option<String> = None
emptyValue
// None

Let’s write a function that may or not give us a string, thus returning Option<String>:

fun maybeItWillReturnSomething(flag: Boolean): Option<String> =
   if (flag) Some("Found value") else None

Using getOrElse we can provide a default value "No value" when the optional argument None does not exist:

val value1 = maybeItWillReturnSomething(true)
val value2 = maybeItWillReturnSomething(false)
value1.getOrElse { "No value" }
// Found value
value2.getOrElse { "No value" }
// No value

Creating a Option<T> of a T?. Useful for working with values that can be nullable:

val myString: String? = "Nullable string"
val option: Option<String> = Option.fromNullable(myString)

Checking whether option has value:

value1 is None
// false
value2 is None
// true

Option can also be used with when statements:

val someValue: Option<Double> = Some(20.0)
val value = when(someValue) {
   is Some -> someValue.t
   is None -> 0.0
}
value
// 20.0
val noValue: Option<Double> = None
val value = when(noValue) {
   is Some -> noValue.t
   is None -> 0.0
}
value
// 0.0

An alternative for pattern matching is performing Functor/Foldable style operations. This is possible because an option could be looked at as a collection or foldable structure with either one or zero elements.

One of these operations is map. This operation allows us to map the inner value to a different type while preserving the option:

val number: Option<Int> = Some(3)
val noNumber: Option<Int> = None
val mappedResult1 = number.map { it * 1.5 }
val mappedResult2 = noNumber.map { it * 1.5 }
mappedResult1
// Some(4.5)
mappedResult2
// None

Another operation is fold. This operation will extract the value from the option, or provide a default if the value is None

number.fold({ 1 }, { it * 3 })
// 9
noNumber.fold({ 1 }, { it * 3 })
// 1

Arrow also adds syntax to all datatypes so you can easily lift them into the context of Option where needed.

1.some()
// Some(1)
none<String>()
// None
val nullableValue: String? = null
nullableValue.toOption()
// None
val nullableValue: String? = "Hello"
nullableValue.toOption()
// Some(Hello)

Some Iterable extensions are available, so you can maintain a friendly API syntax while avoiding null handling (firstOrNull())

val myList: List<Int> = listOf(1,2,3,4)
myList.firstOrNone { it == 4 }
// Some(4)
myList.firstOrNone { it == 5 }
// None

Sample usage

fun foo() {
    val foxMap = mapOf(1 to "The", 2 to "Quick", 3 to "Brown", 4 to "Fox")

    val ugly = foxMap.entries.firstOrNull { it.key == 5 }?.value.let { it?.toCharArray() }.toOption()
    val pretty = foxMap.entries.firstOrNone { it.key == 5 }.map { it.value.toCharArray() }
    
    //Do something with pretty Option
}

Arrow contains Option instances for many useful typeclasses that allows you to use and transform optional values

Functor

Transforming the inner contents

import arrow.typeclasses.*
import arrow.instances.option.functor.*

Option.functor().run {
  Some(1).map { it + 1 }
}
// Some(2)

Applicative

Computing over independent values

import arrow.instances.option.applicative.*

tupled(Some(1), Some("Hello"), Some(20.0))
// Some(Tuple3(a=1, b=Hello, c=20.0))

Monad

Computing over dependent values ignoring absence

ForOption extensions {
  binding {
   val a = Some(1).bind()
   val b = Some(1 + a).bind()
   val c = Some(1 + b).bind()
   a + b + c
  }
}
//Some(value=6)
ForOption extensions {
  binding {
   val x = none<Int>().bind()
   val y = Some(1 + x).bind()
   val z = Some(1 + y).bind()
   x + y + z
  }
}
//None

Supported type classes

Module Type classes
arrow.aql Count, From, GroupBy, OrderBy, Select, Sum, Union, Where
arrow.mtl.typeclasses FunctorFilter, MonadCombine, MonadFilter, TraverseFilter
arrow.optics.typeclasses Each
arrow.typeclasses ApplicativeError, Applicative, Eq, Foldable, Functor, Hash, MonadError, Monad, Monoid, MonoidK, Semigroup, SemigroupK, Show, Traverse

Credits

Contents partially adapted from Scala Exercises Option Tutorial Originally based on the Scala Koans.