Proposal for the Option/Maybe datatype

Hello!

I took a look at Kotlin and it’s standard library recently and found a lack of the Option/MayBe datatype. It’s not so necessary in Kotlin as in other languages due to compile-time null checking but could have some usage: in my opinion, usage of map on the Option is more clear and concise than checking for null before applying function.

So my question to the Kotlin community and language authors:
Do you want to have a Option/MayBe type in the language standard library?

If the answer is “yes”, I’ll try to implement it, using Scala Option as a guideline for the API.

Mario already has an implementation in his FunKtionale library you might want to look at...

https://github.com/MarioAriasC/funKTionale/tree/master/src/main/kotlin/org/funktionale/option

1 Like

Thank you.  I think another implementation is unnecessary, provided funKTionale is live and maintained.

I took a look at Kotlin and it's standard library recently and found a lack of the Option/MayBe datatype. It's not so necessary in Kotlin as in other languages due to compile-time null checking but could have some usage: in my opinion, usage of map on the Option is more clear and concise than checking for null before applying function.

I'm a n00b in kotlin, but I'd say that Option is a bad idea for an language having also null. It leads to split where some libraries use Option and some use null. Moreover, sometimes you have to deal with nullable Option.

I can’t imagine a case where having Option leads to a better code. But as I said, I’m just a n00b and would love to get corrected.

1 Like

maaartinus wrote:

I’m a n00b in kotlin, but I’d say that Option is a bad idea for an language having also null. It leads to split where some libraries use Option and some use null. Moreover, sometimes you have to deal with nullable Option.

I don’t think so. For example, in Scala there are both null and Option but it seems to me that Option is predominant and null is used mostly for interoperation with Java. I’d rather say that null is a bad idea by itself. Kotlin goes to great lengths to make it less harmful but I still don’t like usage of null for representing absent optional value. Maybe, it’s inertia of my own mind.

,maaartinus wrote:

I can’t imagine a case where having Option leads to a better code. But as I said, I’m just a n00b and would love to get corrected.


It’s matter of taste, of course. But for me

mayBeValue map doSmth

or

mayBeValue forEach doSmth

is more concise than

if (mayBeValue != null) doSmth(mayBeValue)


In the case of multiple values it’s depends on the additional language features.
The code

mayBeA flatMap { a ->
  mayBeB flatMap { b ->
  mayBeC map { c ->
  doSmth(a, b, c)
  }
  }
}

probably, not more readable than

if ((mayBeA != null) && (mayBeB != null) && (mayBeC != null)) {
  doSmth(a, b, c)
}

but

for {
a <- mayBeA
b <- mayBeB
c <- mayBeC
} yield doSmth(a, b, c)

is (for me, at least).

1 Like

I'm a n00b in kotlin, but I'd say that Option is a bad idea for an language having also null. It leads to split where some libraries use Option and some use null. Moreover, sometimes you have to deal with nullable Option.

I don't think so. For example, in Scala there are both null and Option but it seems to me that Option is predominant and null is used mostly for interoperation with Java. I'd rather say that null is a bad idea by itself. Kotlin goes to great lengths to make it less harmful but I still don't like usage of null for representing absent optional value. Maybe, it's inertia of my own mind.

I believe that it's all just a matter of proper language syntax and library support. Option<T> may have a value or not. T? may have a value or not. Can you spot the difference? I can't.

It's matter of taste, of course. But for me

mayBeValue map doSmth

or

mayBeValue forEach doSmth

is more concise than

``

if (mayBeValue != null) doSmth(mayBeValue)

There's no reason to write such complicated things. Even I was able to write this:

import java.util.ArrayList public inline fun <T, R> List<out T>?.myMap(transform: (T) -> R): List<R>? {   if (this==null) return null;   return myMapTo(ArrayList<R>(), transform) } public inline fun <T, R, C : MutableCollection<in R>> List<out T>?.myMapTo(destination: C, transform: (T) -> R): C? {   if (this!=null) {   for (item in this) destination.add(transform(item))   }   return destination } val nullList :List<String>? = null val normalList :List<String>? = listOf("a", "b") fun doSmth(s: String) : String = "$s$s" fun main(args: Array<String>) {

  println(normalList myMap ::doSmth)   println(nullList myMap ::doSmth)

}

I had to define my own null-accepting myMap and use a different name for it. Does anybody know
  • why the standard map doesn't accept null?
  • why I can't overload on T and T?

I’d bet it’s possible to do the same for flatMap, too, why shouldn’t it?

I don't think that something like Optional should be part of a third party library. If it is a supported concept, it should be part of the Kotlin standard library so that all other libraries can use the data type and leverage it for their own APIs. It would be nice if it played nicely together with Java's version of Optional.

I don't mind the idea of Open/Maybe type, although I think it is already availabe in Kotlin because of the nullable types already making it clear what is optional or not.  But, I would never like to see this forced to be used from things like myMap.get("key") since you then have to always dereference.  Extensions can have myMap.getOptional("key") but let's not break the closeness to the Java built-in collection classes.

I find that there are patterns I use now that make me not even think about Optional very often.  For example:


  myMap.ifContains(“key”) { value -> … do something … }
  myMap.ifNotContains(“key”) { … do something else … }

and variations that deal with what present means (null, empty string, empty string once trimmed).

These extensions make my code clearer what I intend anyway than guessing from the if statements (does each if statement really need null check, trim().isNotEmpty()?).  You can also extend to create patterns like:

  val myMap = mapOf(“one” to 1, “two” to 2)
  myMap.ifContains(“one”) {  value ->
  println(value)
  } otherwise {
  println(“not found”)
  }

Or maybe that is backwards for reading and we are extending the wrong thing.  String could be extended to get the words in the right order (for English sentences):

  “one”.isPresentIn(myMap) {  value ->
  println(value)
  } otherwise {
  println(“not found”)
  }

Outside of this use case, I also have patterns like “add a default, but let me know you used the default” where I have data classes like:

  data class PropertyValue(val value: String, val wasDefaulted: Boolean)

And then my property class would have a few varations of getting properties:

  props.getValue(“forProperty”) -> String
  props.getValueElse(“forProperty”) { … generate default … } -> String
  props.getValueElse(“forProperty”, “literalDefault”) -> String
  props.getValueDefaulted(“forProperty”) { … generate default … } -> PropertyValue
  props.getValueDefaulted(“forProperty”, “literalDefault”) -> PropertyValue

No optional needed, and it is clearer for this case than a generic Optional would be.  And I can avoid the new return class if it doesn’t make sense at the call site (sometimes I don’t care if it was defaulted, other times I need to know).

More ways to skin the cat…  

  

1 Like

+1

I don't think that something like Optional should be part of a third party library. If it is a supported concept, it should be part of the Kotlin standard library so that all other libraries can use the data type and leverage it for their own APIs.

I don’t think, something like this should ever exist in an language having also nulls. The more it gets used, the worse, as it creates more and more library inconsistencies.

IMHO Java Optional is just library sweetener substituting lacking language sugar. Kotlin has this sugar.

I might be wrong, but then please anyone show me what can be done with Optional and not with nulls (see my example below).

1 Like

Looking at things the other way around: would it be a good idea if Kotlin treated Java's Optional type as ?

public Optional<String> doStuff(Optional<Integer> someInput)

would be treated by Kotlin as

public fun doStuff(Int? someInput): String?

There would probably need to be some magic going on behind the scenes to make something like that possible and I am wondering that would be something worth having?

1 Like

I see no point in polluting the language with the Option type, when the same feature is achieved at a language-level with explicit nullable types :-)

  val x : String? = getSomeString()

- String? is equivalent to an Optional<String> - Optional.getOrElse() is already there as the default ":?" operator

   val y: String = x :? "default value"

- Optional.map( (T) -> T ) is equivalent to the safe navigation operator ".?"

e.g.:

   //Optional<String> x
   x.map( x -> x.length() )

which maps x into Option<Integer> when x is not None, and maps it into None when it s

is exactly the same as

   val x: Int? = x?.length()

and so on.

3 Likes

It’s been 5 years since the last post, are there any updates regarding this?
It’s now pretty easy to implement Option using sealed classes.

@hhariri have Kotlin team considering adding Option to standard library?
I know there is https://arrow-kt.io/, but I believe Kotlin may benefit from adding a few major standard types like Option or Result to stdlib as common ground for libraries.

We do not have plans to introduce Option type at this moment. As to the class Result, we have it since Kotlin 1.3, though its usages are subject to some limitations, that we hope to eliminate later.

What are those benefits for Kotlin of introducing common Option you are implying?

1 Like

Ok, I did some proof-of-concept.
I wrote basic implementation using sealed classes and a few utility methods.

Basically, in almost every use case what could be done using Option<T> could also be done by T?:

Map::get:

 // Suppose we have a `user -> age` mapping, and want to display a message about user age:
 val ageMap = mapOf("alex" to 20, "olga" to 30, "john" to 40)
 val name = "liza"

 // T?
 val age: Int? = ageMap.get(name)
 val msgByWhen = when (age) {
        null -> "don't know age for $name"
        else -> "$name is $age years old"

// Option<T> 
val ageOption: Option<Int> = ageMap.getOrNone(name)
val msgOptionByWhen = when (ageOption) {
        is Some -> "$name is ${age.value} years old"
        is None -> "don't know age for $name"

Or map:

// T?
val nullable: Int? = 5
val squared = nullable?.let { it * it }

// Option<T>
val option: Option<Int> = Some(5)
val squared = option.map { it * it }

List map + filter:

names.mapNotNull { if (it.startsWith("a")) { it.capitalize() } else { null } }
names.mapNotNone { if (it.startsWith("a")) { Some(it.capitalize()) } else { None }}

A rare case when it’s not true is when you need to have Option<Option<T>> (e.g. you want to distinguish cases user not found and user found, but it didn’t enter their age).
In most of cases syntax is pretty much the same.

I still feel that Option is a better choice for a language in general (I like how it is used in rust std lib), but I should admit that for a language that already has nullable, syntax for it and a lot of utility functions, adding option probably does not make sense.

2 Likes

I’m curious. Do you have any reasons for that? Option and kotlins nullable types are functionality wise the same, except that you can’t have Option<Option<T>> with nullable types. But then this shouldn’t be used in the first place. So in the end it comes basically down to syntax preference, but even this is mostly the same if the language has good support for whatever it chooses.
I mean would kotlin be any different if they changed T? to mean Optional<T>? Not really. Ok java interop would most likely break, but this is something “outside” of kotlin.

1 Like

In terms of what can you do with Optional vs nullable - no, it would be the same.

The main difference in my view that if you use algebraic data type in language standard library, you signalize your users that language has this concept as a first-class citizen and promote your users to use it.

When you start to write rust, stuff like Option and Result is everywhere. In kotlin sealed classes is an advanced topic (at least as I see).

How many kotlin programmers would write a type for json entry as sealed class of array/object/number/string/boolean/null? I doubt that many. In languages like rust it’s a standard approach.

That’s exactly the reason why Kotlin should not have Optional. To signal their users that nullable should be used instead.

2 Likes

in practice null-object (empty object) is enough for rx and java-interop cases and more transparent|redable, btw it is one of optional implementation and it’s pretty compact, it will be perfect when contracts released (for autocast instance with checked isEmpty|isNull to value type just like current nullability checking works)
as option we also have nice and short GitHub - gojuno/koptional: Minimalistic Optional type for Kotlin that tries to fit its null-safe type system as smooth as possible. implementation

The funKTionale library moved here.