drekbour
drekbour

Reputation: 3081

Kotlin multiplatform support for Optional

I'm working with a Java API now converted into multiplatform Kotlin. It used to use java.lang.Optional as the return type of many calls. I understand this is not the idiomatic Kotlin-way (see discussion) but this is an existing API, Optional stays (also it isn't a bad choice for the Java-facing client). My question is how?

Note: The code only needs to return Optional.of(x) or return Optional.empty() to the external API. Any internal uses will be purged.

Upvotes: 3

Views: 786

Answers (1)

hotkey
hotkey

Reputation: 147951

At this point, Kotlin doesn't allow providing an actual typealias for an expected class with a companion object by using a Java class with matching static declarations. Follow this issue for updates: KT-29882.

For now, you can workaround that by declaring the factory functions separately, outside the expected Optional class, as follows:

expect class Optional<T : Any> {
    fun get(): T
    fun isPresent(): Boolean
    /* ... */
}

expect object Optionals {
    fun <T : Any> of(t: T): Optional<T>
    fun empty(): Optional<Nothing>
}

That should not necessarily be an object, you could just use top-level functions.

Then, on the JVM, you would have to provide an actual typealias for the Optional class and, additionally, provide the trivial actual implementation for the Optionals object:

actual typealias Optional<T> = java.util.Optional<T>

actual object Optionals {
    actual fun <T : Any> of(t: T): Optional<T> = java.util.Optional.of(t)
    actual fun empty(): Optional<Nothing> = java.util.Optional.empty()
}

As for not providing an implementation for the non-JVM platforms, I doubt it's possible, as that would require some non-trivial compile-time transformations of the Optional usages to just the nullable type. So you would want something like this:

actual typealias Optional<T> = T?

which is now an error:

Type alias expands to T?, which is not a class, an interface, or an object

So you actually need a non-JVM implementation. To avoid duplicating it for every non-JVM target, you can declare a custom source set and link it with the platform-specific source sets, so they get the implementation from there:

build.gradle.kts

kotlin {
    /* targets declarations omitted */

    sourceSets {
        /* ... */

        val nonJvmOptional by creating {
            dependsOn(getByName("commonMain"))
        }
        configure(listOf(js(), linuxX64())) { // these are my two non-JVM targets
            compilations["main"].defaultSourceSet.dependsOn(nonJvmOptional)
        }
    }
}

Then, inside this custom source set (e.g. in src/nonJvmOptional/kotlin/OptionalImpl.kt) you can provide an actual implementation for the non-JVM targets.

Here's a minimal project example on Github where I experimented with the above: h0tk3y/mpp-optional-demo

Upvotes: 2

Related Questions