The Option Module
The Option type is a well known and often used type, but at least for me, most of the
time I just used Option.map and Option.bind and ignored functions like Option.exists,
Option.filter, Option.fold and so on. I spent some time with those functions to understand
when those are useful.
defaultArg
The first function i look at is actually not in the Option module. It is the defaultArg
function. With defaultArg we can extract an option type and provide a default value
in the case we had no value.
|
|
One think I dislike is the order of the arguments. Because the Option type is expected first
as an argument, defaultArg is unsuitable for piping or composition. That’s why I most often
add a orElse function to the Option module myself.
|
|
exists & forall
I must admit, i never looked closer at those functions. It is obvious that those functions are ported from the List/Array/Seq module. But because an option never contains more than one element, I never looked closer to those functions. The truth is, because we know that an option only contains either no value or a single value, the meaning of those functions just change.
Let’s look at some typical code with no option at all that you will sometimes have. You just check a variable if some statement is true or false and you use that for branching.
|
|
What do you do, when x is an option? Then you can use Option.exists
|
|
Generally speaking. With Option.exists you can check an option for a condition. None
is treated as false. Naming the function check or some other name than
exists would probably have been a better name. With some helper functions we can enhance
the validation process.
|
|
Option.forall is basically the same, only that None is threaten as true instead of false.
But I must admit, I cannot come up with a useful example for forall.
filter
In my last example I added a second check. While two checks are still somehow okay in terms of readability it can become unhandy fast. Wouldn’t it be better if we could chain the operations?
filter gives us exactly this ability. Instead of returning true or false it just returns
an option again. When the predicate we provided returns true we just get back the original value
unchanged. Otherwise we get None.
|
|
We also can use Option.filter to easily turn a type into an option based on a predicate.
|
|
fold
I started with defaultArg and implemented orElse. But overall we could replace those with
fold. Besides the option itself, fold expects two additional arguments. A function and an
accumulator. fold either executes the function or it returns the accumulator as the default value.
|
|
In general the idea of fold is that we can return any other type that we want. fold is
a general way to convert types. If that sounds a lot like map. The difference is that map
still only converts the wrapped type and we still get an option back. But with fold we
directly get the wrapped type back. It just means that whenever you use a map and then
orElse. You also could use fold instead.
|
|
Up to this point I always ignored the accumulator argument, and just used the accumulator
as the default argument. But in general it means whenever you want to use a function
where one argument is an option you could probably use fold. In general fold works
nicely together with binary functions.
|
|
We either execute our function with two arguments, or if the second argument is a None we return
the first argument as the default value. The arguments itself don’t need to be of the same types.
Validation
With very few helper functions we could built a small validation framework that uses the option type.
Most of them are just other names instead of map, filter or bind.
|
|