initial commit

This commit is contained in:
kageru 2020-10-02 16:13:26 +02:00
commit 1cdf942f9d
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
3 changed files with 100 additions and 0 deletions

46
Option.kt Normal file

@ -0,0 +1,46 @@
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS", "UNCHECKED_CAST")
inline class Option<A> private constructor(val value: Any?) {
inline fun <R> map(op: (A) -> R): Option<R> =
if (this.value === EmptyOptionValue) {
this as Option<R>
} else {
Option.just(op(this.value as A))
}
inline fun filter(op: (A) -> Boolean): Option<A> =
if (this.value === EmptyOptionValue || !op(this.value as A)) {
none()
} else {
this
}
inline fun <R> 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<Any>(EmptyOptionValue)
fun <A> none() = EMPTY as Option<A>
fun <A> just(value: A) = Option<A>(value)
}
}
/**
* Marker for empty options.
* This is necessary to allow nullable types in the option.
* TODO: Somehow dont expose this.
*/
object EmptyOptionValue

40
OptionTest.kt Normal file

@ -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<Int>().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<Int>().fold({ error("Should not be executed") }, { 2 }), 2)
}
fun shouldHandleNullableTypes() {
assertEquals(Option.just<Int?>(null).map { it ?: 5 }.getOrNull(), 5)
assertEquals(
Option.just<List<String>>(emptyList())
.map { it.firstOrNull() }
.fold({ it }, { error("Should not be executed") }),
null
)
}
private fun <T> assertEquals(x: T, y: T) {
if (x != y) {
error("$x was not equal to $y")
}
}

14
README.md Normal file

@ -0,0 +1,14 @@
# inline-option
An implementation of an `Option<A>` 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<Int?>`).
I don’t think those are ever a good idea to have, but I saw no reason not to support them.