How to traverse optional values in Scala, Kotlin and Java
Let’s study a real life, quite simple problem :
We have a list of optional values, and we want to produce an optional single output, made from present values (if there is any). We will also join values using a separator character.
Exemple 1 : Using a List(Some("A"), None, Some("B"))
should produce Some("A,B")
.
Exemple 2 : Using List(None)
should produce None
.
Scala version
We can use the flip
method from ZIO-prelude (or the sequence method from Cats) to turn a List[Option[String]]
into a Option[List[String]]
.
val list = List(Some("A"), None, Some("B"))
val flippedList = list.filter(_.isDefined).flip // <- Option(List("A","B"))
flippedList.map(_.mkString(",")) // <- Some("A,B")
Note that if we just wanted to produce a String (that may be empty), we could have done this to remove the Option wrapper and undefined values :
val flattenList = List(Some("A"), None, Some("B")).flatten // <- List("A","B")
flattenList.mkString(",")
Kotlin version
In Kotlin, we usually just use nullable types to replace Option.
val list = listOf("a", null, "b")
val result = list.filterNotNull().joinToString(separator=",").ifBlank { null }
Java version
In Java we will use the stream API, which is a bit more verbose but still working fine. Stream::flatMap will remove all the undefined elements and remove the Optional wrapper (like Scala’s flatten).
List<Optional<String>> list = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));
var result = Optional.of(
list.stream()
.flatMap(Optional::stream)
.collect(Collectors.joining( "," ))
).filter(Predicate.not(String::isEmpty));