Option
type (most languages), you've undoubtedly seen a NullPointerException
or something similar pop up at runtime, e.g. the anxiety-inducing Cannot read property '…' of undefined
in Javascript land. There's no guarantee that a variable references an actual value. From here on, I'll use "empty value" to mean the absence of a value instead of using one language-specific keyword like null
, undefined
, or nil
.thing
's value is an empty value? The world blows up. Okay, so that's an easy fix right?-1
1. You look through the documentation (if it exists) and read about it thereOption
to signify as much (we'll get into the details of precisely how to do that later).Option
and the caller will be forced to deal with that possibility.Option[A]
is an option of type A
--or an A
option. You can think of Option
as a container for some value. In a concrete example, Option[Int]
is an option of type Int, or an Int option. If I say a variable x
is of type Option[Int]
you know that x
may or may not actually have a value.Option
is an abstract class so we can't create an instance of Option
directly. We use one of its two implementations: None
or Some[A]
. It should be apparent what those two mean . None
means a reference has no value i.e. null
. Some[A]
means a reference does have a value and the type of that value is A
.None
is simple (None
is a Scala object
so we can use it like a normal value):val x: Option[Int] = None
-- make a variable x
and explicitly define that it has no value, which can be used anywhere where options of Int
s are accepted. An alternative could be Option.empty[Int]
, which does the same thing. You may have to use this alternative form when it's not clear to the compiler what Option
type to infer.Some[A]
:val y: Option[Int] = Some(42)
--make a variable y and say it has the value 42
, which can be used anywhere where options of Int
s are accepted. The constructor for Some
takes one value of the type your option is parameterized on--in this case Int
.Option
object's apply
method to wrap a value in an option to prevent those nasty null
s from invading Scala land:Option[Int]
s together, so how do we work with the actual underlying value in an option -- an Int
in this case -- if it in fact has one?nameString: String
is a bad variable name because it's superfluous --name
is more appropriate and concise.nameOption
, nameOpt
, nameO
. My go-to is maybeName
. If you're looking for a great reason here, I don't have one. It's probably because that's how most option variables were named in the codebase I worked on when I started using Scala and now I just use it out of habit.name: Option[String]
. Let's get back to more important stuff now..isDefined
(or its inverse .isEmpty
) and .get
, which either returns the contained value for a Some
or throws a NoSuchElementException
(😱) if it's a None
..get
. Ever. No seriously, do not ever use .get
on an option. The most important reason is that you run the risk of throwing an exception where you don't expect it. In this example we're checking if the option is defined first, so the .get
is technically safe here, but it's easy to forget to perform that check every time..get
..getOrElse
method. This is a pretty common method of handling options..get
. Just like pattern matching in general, using it on options allows you to easily take specific actions on all the different possibilities of values.Some
with pattern matching as shown above.None
), or a collection with just one element whose type is the A
in Option[A]
. Now that we have this special type of collection that can only be one element long, what can we do with it? Basically everything that a collection can do, which is what makes this comparison so powerful.map
, foreach
, flatMap
, flatten
, fold
, filter/filterNot
, and collect
. We'll also cover .getOrElse
a little more and a similar method, .orElse
.maybePlayer
has a value. Let's get into our options-as-collections mindset: mapping on an empty collection always gives back an empty collection--in the case of options, None
s always map to None
. Some
s work exactly like you'd expect a normal collection to map, just with a maximum of one element. We apply the function p => p.name
to each element of the "collection", so empty options stay as is, and non-empty options get the function applied to them and we get the result of the function application.maybePlayer.map(_.name)
for some real clean, concise beauty..getOrElse
that we used before since mapping returns another option..map
returns an Option
, we can chain on a .getOrElse
which will evaluate to the person's name if maybePlayer
is defined, or "No player" otherwise..orElse
, which, lets us provide a default value (just like .getOrElse
). However, instead of providing a default value of type A
we provide one of type Option[A]
. While .getOrElse
lets us either extract the contained value or provide a default both of type A
, .orElse
gives us the still-contained original value or a completely different option as a default. We can easily chain on as many as we'd like to get us many different possibilities. For whatever reason, you'll often see .orElse
called using infix notation, i.e. without a dot..foreach
works almost exactly like .map
does (just like its collection-based equivalent) except it's only meant for performing some side-effect, i.e. returns Unit
. If the option has a value, execute some function, otherwise do nothing.Option[Option[A]]
, which can quickly become a nuisance to use. Flatten should be straight-forward if you've ever used it on a collection: .flatMap
is equivalent to .map(...).flatten
.Player
class, height
could be missing, since it's an Option[Double]
--flat mapping gives us a clean way to extract that data..getOrElse
in the function passed to .map
. The compiler infers an Any
because the resulting type when evaluating just the map is Option[Option[Double]]
, so calling .get
on it would give back Option[Double]
and we're providing a Double
as a default value. This forces us to also throw on a .getOrElse
in the map to get type we want-- so use flat map instead!.map(...).getOrElse
pattern we saw before except in reverse order -- the default value comes first. Let's compare the two approaches:.map
& .getOrElse
route because its more intuitive to me to have the default value defined after. Whatever floats your boat here is fine..filter
, if an element satisfies a predicate, it remains in, otherwise it gets filtered out . .filterNot
simply inverts the predicate, as its name implies. Since there's only one "element" in an option, the predicate is only checked once. Filtering an option can make a Some
turn into either a Some
or None
, but filtering on a None
will always give back a None
..collect
on options is mostly not that useful. All operations you can do with a collect you could also do with a map. What separates them is that collect accepts a partial function whereas map accepts a plain function (which means it can also accept a partial function), and even then the implementation of collect on Option
actually calls lift
on the partial function, which converts it to a plain function. That implementation detail is actually the one thing that collect has over map -- you won't run into MatchError
s with collect.MatchError
s anyway, so I typically find it of little utility.elem
if the option is not empty AND the underlying value of the option is equal to elem
. This explanation should allow you think up what the truth table looks like for this. If the option is empty (None
) it can't contain anything! If you think of this expression in plain english you can easily tell what the evaluation should be by asking does my option contain this value?.contains
.p
, which gets passed as an argument to the methods. p
is a function A => Boolean
, i.e. a function that accepts a value of your option's contained type, A
, and returns a Boolean. It's p
for predicate -- we're checking whether an element of type A
satisfies the predicate p
, which returns true or false accordingly.p
. If this looks familiar, it's because it's almost exactly the same as .contains
. . contains
is basically a specific case of .exists
where the predicate is this.get == elem
..contains
. This is a convenient and concise way to do simple Boolean checks on the underlying value of an option.p
. The key word here is contained, because an option that doesn't have any values (None
) doesn't have any values that need to satisfy the predicate. This is expressed with the ||
in the implementation. An empty option OR a contained value that satisfies the predicate both make this evaluate to true..exists
is that we're using .getOrElse(true)
instead of .getOrElse(false)
.final
, and reorganized the methods into what I think are logical groupings to provide you with the cleanest, simplest version of the source without changing the core of it..orNull
and .flatten
aren't obvious, especially with the <:<
operator. If you'd like a good explanation of how that implicit parameter works, I'd recommend this excellent and thorough explanation. But if you just want to understand how options work, you can mostly ignore it.Option
: