Optiontype (most languages), you've undoubtedly seen a
NullPointerExceptionor something similar pop up at runtime, e.g. the anxiety-inducing
thing's value is an empty value? The world blows up. Okay, so that's an easy fix right?
-11. You look through the documentation (if it exists) and read about it there
Optionto signify as much (we'll get into the details of precisely how to do that later).
Optionand the caller will be forced to deal with that possibility.
Option[A]is an option of type
Aoption. You can think of
Optionas 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
xis of type
Option[Int]you know that
xmay or may not actually have a value.
Optionis an abstract class so we can't create an instance of
Optiondirectly. We use one of its two implementations:
Some[A]. It should be apparent what those two mean .
Nonemeans a reference has no value i.e.
Some[A]means a reference does have a value and the type of that value is
Noneis simple (
Noneis a Scala
objectso we can use it like a normal value):
val x: Option[Int] = None-- make a variable
xand explicitly define that it has no value, which can be used anywhere where options of
Ints 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
Optiontype to infer.
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
Ints are accepted. The constructor for
Sometakes one value of the type your option is parameterized on--in this case
applymethod to wrap a value in an option to prevent those nasty
nulls from invading Scala land:
Option[Int]s together, so how do we work with the actual underlying value in an option -- an
Intin this case -- if it in fact has one?
nameString: Stringis a bad variable name because it's superfluous --
nameis more appropriate and concise.
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
.get, which either returns the contained value for a
Someor throws a
NoSuchElementException(😱) if it's a
.get. Ever. No seriously, do not ever use
.geton 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
.getis technically safe here, but it's easy to forget to perform that check every time.
.getOrElsemethod. 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.
Somewith pattern matching as shown above.
None), or a collection with just one element whose type is the
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.
collect. We'll also cover
.getOrElsea little more and a similar method,
maybePlayerhas 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,
Nones always map to
Somes work exactly like you'd expect a normal collection to map, just with a maximum of one element. We apply the function
p => p.nameto 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.
.getOrElsethat we used before since mapping returns another option.
Option, we can chain on a
.getOrElsewhich will evaluate to the person's name if
maybePlayeris 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
Awe provide one of type
.getOrElselets us either extract the contained value or provide a default both of type
.orElsegives 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
.orElsecalled using infix notation, i.e. without a dot.
.foreachworks almost exactly like
.mapdoes (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:
.flatMapis equivalent to
heightcould be missing, since it's an
Option[Double]--flat mapping gives us a clean way to extract that data.
.getOrElsein the function passed to
.map. The compiler infers an
Anybecause the resulting type when evaluating just the map is
Option[Option[Double]], so calling
.geton it would give back
Option[Double]and we're providing a
Doubleas a default value. This forces us to also throw on a
.getOrElsein the map to get type we want-- so use flat map instead!
.map(...).getOrElsepattern we saw before except in reverse order -- the default value comes first. Let's compare the two approaches:
.getOrElseroute 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 .
.filterNotsimply 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
Someturn into either a
None, but filtering on a
Nonewill always give back a
.collecton 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
lifton 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
MatchErrors with collect.
MatchErrors anyway, so I typically find it of little utility.
elemif 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?
p, which gets passed as an argument to the methods.
pis 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
pfor predicate -- we're checking whether an element of type
Asatisfies the predicate
p, which returns true or false accordingly.
p. If this looks familiar, it's because it's almost exactly the same as
. containsis basically a specific case of
.existswhere 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.
.existsis that we're using
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.
.flattenaren'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.