I was having a look at the Arrow library found here. Why would ever want to use an Option
type instead of Kotlin's built in nullables?
-
1already read the docs? http://arrow-kt.io/docs/datatypes/option/ – s1m0nw1 Feb 20 '18 at 22:45
-
8@s1m0nw1 That states "*Kotlin tries to solve the problem by getting rid of null values altogether and providing its own special syntax Null-safety machinery based on `?`.*" and "*Arrow models the absence of values through the Option datatype similar to how Scala, Haskell and other FP languages handle optional values.*" It does not provide any comparison between the two approaches. – Bergi Feb 20 '18 at 23:00
3 Answers
I have been using the Option
data type provided by Arrow for over a year, and there at the beginning, we did the exact same question to ourselves. The answer follows.
Option vs Nullable
If you compare just the option
data type with nullables
in Kotlin, they are almost even. Same semantics (there is some value or not), almost same syntax (with Option you use map
, with nullables you use safe call operator).
But when using Options
, you enable the possibility to take benefits from the arrow ecosystem!
Arrow ecosystem (functional ecosystem)
When using Options
, you are using the Monad Pattern
. When using the monad pattern with libraries like arrow, scala cats, scalaz, you can take benefits from several functional concepts. Just 3 examples of benefits (there is a lot more than that):
1. Access to other Monads
Option
is not the only one! For instance, Either
is a lot useful to express and avoid to throw Exceptions. Try
, Validated
and IO
are examples of other common monads that help us to do (in a better way) things we do on typical projects.
2. Conversion between monads + abstractions
You can easily convert one monad to another. You have a Try
but want to return (and express) an Either
? Just convert to it. You have an Either
but doesn't care about the error? Just convert to Option
.
val foo = Try { 2 / 0 }
val bar = foo.toEither()
val baz = bar.toOption()
This abstraction also helps you to create functions that doesn't care about the container (monad) itself, just about the content. For example, you can create an extension method Sum(anyContainerWithBigDecimalInside, anotherContainerWithBigDecimal)
that works with ANY MONAD (to be more precise: "to any instance of applicative") this way:
fun <F> Applicative<F>.sum(vararg kinds: Kind<F, BigDecimal>): Kind<F, BigDecimal> {
return kinds.reduce { kindA, kindB ->
map(kindA, kindB) { (a, b) -> a.add(b) }
}
}
A little complex to understand, but very helpful and easy to use.
3. Monad comprehensions
Going from nullables to monads is not just about changing safe call operators to map
calls. Take a look at the "binding" feature that arrow provides as the implementation of the pattern "Monad Comprehensions":
fun calculateRocketBoost(rocketStatus: RocketStatus): Option<Double> {
return binding {
val (gravity) = rocketStatus.gravity
val (currentSpeed) = rocketStatus.currentSpeed
val (fuel) = rocketStatus.fuel
val (science) = calculateRocketScienceStuff(rocketStatus)
val fuelConsumptionRate = Math.pow(gravity, fuel)
val universeStuff = Math.log(fuelConsumptionRate * science)
universeStuff * currentSpeed
}
}
All the functions used and also the properties from rocketStatus
parameter in the above example are Options
. Inside the binding
block, the flatMap
call is abstracted for us. The code is a lot easier to read (and write) and you don't need to check if the values are present, if some of them is not, the computation will stop and the result will be an Option with None
!
Now try to imagine this code with null verifications instead. Not just safe call operators
but also probably if null then return
code paths. A lot harder isn't it?
Also, the above example uses Option
but the true power about monad comprehensions as an abstraction is when you use it with monads like IO in which you can abstract asynchronous code execution in the exact same "clean, sequential and imperative" way as above :O
Conclusion
I strongly recommend you to start using monads like Option
, Either
, etc as soon as you see the concept fits the semantics you need, even if you are not sure if you will take the other big benefits from the functional ecosystem or if you don't know them very well yet. Soon you'll be using it without noticing the learning-curve. In my company, we use it in almost all Kotlin projects, even in the object-oriented ones (which are the majority).

- 9,564
- 146
- 81
- 122

- 9,475
- 5
- 65
- 73
-
1A few nullable extension functions and the rocket status stuff can be reduced to `rocketStatus.gravity?.pow(rocketStatus.fuel)?.times(calculateRocketScienceStuff(rocketStatus))?.log2()?.times(rocketStatus.currentSpeed)` which in my opinion is much simpler and it ihas zero cost. – Fleshgrinder Jan 26 '20 at 12:16
-
5@Fleshgrinder it may be personal preference but for me this code is a lot harder to understand and to debug than the imperative version showed in my example. Also if you need to write extension functions (and obviously to mantain it) it has a cost. – fabriciorissetto Feb 18 '20 at 14:35
-
And that was an isolated example. With monad comprehensions we can do other things, like for example to abstract in the same simple and imparative way asynchronous operations – fabriciorissetto Feb 18 '20 at 14:41
Disclaimer: If you really want to have a detailed talk about why Arrow is useful, then please head over to https://soundcloud.com/user-38099918/arrow-functional-library and listen to one of the people who work on it. (5:35min)
The people who create and use that library simple want to use Kotlin differently than the people who created it and use "the Option datatype similar to how Scala, Haskell and other FP languages handle optional values".
This is just another way of defining return types of values that you do not know the output of.
Let me show you three versions:
nullability in Kotlin
val someString: String? = if (condition) "String" else null
object with another value
val someString: String = if (condition) "String" else ""
the Arrow version
val someString: Option<String> = if (condition) Some("String") else None
A major part of Kotlin logic can be to never use nullable types like String?
, but you will need to use it when interopting with Java. When doing that you need to use safe calls like string?.split("a")
or the not-null assertion string!!.split("a")
.
I think it is perfectly valid to use safe calls when using Java libraries, but the Arrow guys seem to think different and want to use their logic all the time.
The benefit of using the Arrow logic is "empowering users to define pure FP apps and libraries built atop higher order abstractions. Use the below list to learn more about Λrrow's main features".

- 14,027
- 4
- 48
- 70

- 114,516
- 58
- 291
- 402
-
10To the question "why should I use `Option` instead of nullables" I would expect concrete reasons/advantages, especially given the additional syntax overhead and non-conformity with standard types. However this answer merely states "so you can use Kotlin in a different way". – TheOperator Jun 25 '19 at 11:41
One thing other answers haven't mentioned: you can have Option<Option<SomeType>>
where you can't have SomeType??
. Or Option<SomeType?>
, for that matter. This is quite useful for compositionality. E.g. consider Kotlin's Map.get
:
abstract operator fun get(key: K): V?
Returns the value corresponding to the given key, or
null
if such a key is not present in the map.
But what if V
is a nullable type? Then when get
returns null
it can be because the map stored a null value for the given key or because there was no value; you can't tell! If it returned Option<V>
, there wouldn't be a problem.

- 1
- 1

- 167,066
- 35
- 309
- 487
-
But you can use `Optional
` in this case and you have `containsKey`. – Fleshgrinder Jan 26 '20 at 12:02 -
11. Yes, it's explicitly mentioned in the answer. But then you still need `Option`, which is what the question is about. 2. Using `containsKey` is worse in at least two ways: accessing the map twice isn't free; in a concurrent program the key can be removed by another thread between `containsKey` and `get` calls. – Alexey Romanov Jan 26 '20 at 18:34
-
I didn't consider `Option` and `Optional ` to be the same. Concurrency is a different issue, you have synchronized for that. In any event, it's an extremely narrow use case that I've only ever encountered in caches. – Fleshgrinder Jan 27 '20 at 19:17
-
If you mean the Java `Optional`, then of course `Optional
` won't work; it doesn't allow nulls. – Alexey Romanov Jan 27 '20 at 21:12 -
`val value: String? = mapOf
>()["k"]?.orElse(null)` goal achieved as we can now distinguish between missing key (get returns null) and a null value (optional is empty). This is how pretty much every cache implementation works that allows null values to be stored. – Fleshgrinder Jan 28 '20 at 10:07