Heyo! Inspired by ArrowKt’s monad comprehensions,...
# getting-started
r
Heyo! Inspired by ArrowKt’s monad comprehensions, I wanted to implement one for Kotlin’s nullable types. The problem is that I don’t really understand functional interfaces. My goal: Have a
bind
extension function on all nullable types, that is only available inside of a
nullable
block. I tried to do it like this, but the
bind
function is not reachable. What’s the proper way to do this?
Copy code
class BindNullableException : IllegalAccessException()

fun interface NullableBlock<A> : () -> A {
    fun <A> A?.bind() = this ?: throw BindNullableException()
}

fun <A> nullable(block: NullableBlock<A>): A? =
    try {
        block()
    } catch (e: BindNullableException) {
        null
    }

fun main() {

    val a = getNullableA().bind() // should not work

    val nullableC = nullable {
        val a = getNullableA().bind() // should work
        val b = getNullableB().bind()
        getNullableC(a, b)
    }
}

fun getNullableA(): String? = null
fun getNullableB(): String? = null
fun getNullableC(a: String, b: String): String? = null
t
Not sure if this could help you, but Arrow has an
Option<T>
type that does exactly what you want, maybe you can look at its source code to understand how it works internally: https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/-option/
I doubt that they use exceptions at all in their implementation because they explicitly advocate against them : https://arrow-kt.io/docs/patterns/error_handling/#the-issues-with-exceptions
r
Yeah, they do it exactly like this, functional interface and throwing a “ShortCircuit” exception. Instead of
nullable
being a function that takes a block, in their implementation it’s an invokable object though. I’m also not sure why their monad comprehensions are suspend functions, that seems unnecessary?
Figured it out, this works:
Copy code
class BindNullableException : IllegalAccessException()

class NullableBlock<A>(val func: NullableBlock<A>.() -> A?) : () -> A {
    fun <B> B?.bind(): B = this ?: throw BindNullableException()
    override fun invoke(): A = func() ?: throw BindNullableException()
}

@Suppress("ClassName")
object nullable {
    operator fun <A> invoke(func: NullableBlock<A>.() -> A?): A? = try {
        NullableBlock(func)()
    } catch (e: BindNullableException) {
        null
    }
}

fun main() {
    val cantWork = getNullableA().bind() // should not work
    val nullableC = nullable {
        val a = getNullableA().bind() // should work
        val b = getNullableB(a).bind()
        getNullableC(a, b)
    }
    println("Got $nullableC")
}

fun getNullableA(): String? = null
fun getNullableB(a: String): String? = null
fun getNullableC(a: String, b: String): String? = null