commit 1cdf942f9dedf89f5f07dc568a2bb6160291a168 Author: kageru Date: Fri Oct 2 16:13:26 2020 +0200 initial commit diff --git a/Option.kt b/Option.kt new file mode 100644 index 0000000..890521d --- /dev/null +++ b/Option.kt @@ -0,0 +1,46 @@ +@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS", "UNCHECKED_CAST") +inline class Option private constructor(val value: Any?) { + + inline fun map(op: (A) -> R): Option = + if (this.value === EmptyOptionValue) { + this as Option + } else { + Option.just(op(this.value as A)) + } + + inline fun filter(op: (A) -> Boolean): Option = + if (this.value === EmptyOptionValue || !op(this.value as A)) { + none() + } else { + this + } + + inline fun fold(ifPresent: (A) -> R, ifEmpty: () -> R): R = + if (this.value === EmptyOptionValue) { + ifEmpty() + } else { + ifPresent(this.value as A) + } + + fun getOrNull(): A? = + if (this.value === EmptyOptionValue) { + null + } else { + this.value as A + } + + companion object { + private val EMPTY = Option(EmptyOptionValue) + + fun none() = EMPTY as Option + + fun just(value: A) = Option(value) + } +} + +/** + * Marker for empty options. + * This is necessary to allow nullable types in the option. + * TODO: Somehow don’t expose this. + */ +object EmptyOptionValue diff --git a/OptionTest.kt b/OptionTest.kt new file mode 100644 index 0000000..b459ca7 --- /dev/null +++ b/OptionTest.kt @@ -0,0 +1,40 @@ + + +fun main() { + testMap() + testFilter() + testFold() + shouldHandleNullableTypes() +} + +fun testMap() { + assertEquals(Option.just(3).map { it * 2 }.map { "$it" }.getOrNull(), "6") + assertEquals(Option.just(listOf(1)).map { it.first() }.getOrNull(), 1) + assertEquals(Option.none().map { error("Should not be executed") }.getOrNull(), null) +} + +fun testFilter() { + assertEquals(Option.just(6).filter { it > 5 }.getOrNull(), 6) + assertEquals(Option.just(6).filter { it < 5 }.getOrNull(), null) +} + +fun testFold() { + assertEquals(Option.just(2).map { it * 2 }.fold({ it / 2 }, { error("Should not be executed") }), 2) + assertEquals(Option.none().fold({ error("Should not be executed") }, { 2 }), 2) +} + +fun shouldHandleNullableTypes() { + assertEquals(Option.just(null).map { it ?: 5 }.getOrNull(), 5) + assertEquals( + Option.just>(emptyList()) + .map { it.firstOrNull() } + .fold({ it }, { error("Should not be executed") }), + null + ) +} + +private fun assertEquals(x: T, y: T) { + if (x != y) { + error("$x was not equal to $y") + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f6ab65 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# inline-option +An implementation of an `Option` type in Kotlin using the `inline class` feature. +See https://kotlinlang.org/docs/reference/inline-classes.html + +It’s just a small proof of concept which is why I decided to omit the usual gradle bloat. +Build and test via: + +```sh +$ kotlinc-native Option.kt OptionTest.kt +$ ./program.kexe +``` + +The implementation uses an empty value placeholder to allow for nullable types (i.e. `Option`). +I don’t think those are ever a good idea to have, but I saw no reason not to support them.