Hello. Is this kind of syntax (or something simil...
# language-proposals
m
Hello. Is this kind of syntax (or something similar) already been discussed? The purpose is saving nested let expressions when we want an expression to be be null if any one of a set variables on which it depends are null:
Copy code
fun f(a: A, b: B): T = TODO()

fun g(a: A?, b: B?) {
    val x = f(a?, b?)
}
where
x
has type
T?
and is
null
if either a or b or both are
null
, instead of
Copy code
fun g(a: A?, b: B?) {
    val x = a?.let { actualA -> b?.let { actualB -> f(actualA, actualB) } }
}
or
Copy code
fun g(a: A?, b: B?) {
    val x = if (a != null && b != null) f(a, b) else null
}
which would not work if
a
or
b
were
var
, or
Copy code
fun g(a: A?, b: B?) {
    val x = a?.let { b?.let { f(a, b) } }
}
which is a bit silly and would not work if
a
or
b
were
var
. Not groundbreaking but I often would like it.
k
Arrow
library has a solution for this called
nullable binding
https://github.com/arrow-kt/arrow/blob/85862e5629f19c1858891904044a864308d81ebe/arrow-libs/core/arrow-core-data/src/main/kotlin/arrow/core/computations/nullable.kt Usage:
Copy code
nullable {
  val aa = a.bind()
  val bb = b.bind() 
  f(aa, bb)
} // if a or b is null shortcircuit to null, if both non null returns `f(aa, bb)`
m
Thanks for mentioning that, @kioba. I think that if someone is already using Arrow, then that's useful.
k
If you want to avoid using
arrow
you can roll your own implementation:
Copy code
@OptIn(ExperimentalContracts::class)
inline fun <V> nullable(crossinline block: NullableBinding.() -> V): V? {
    contract {
        callsInPlace(block, EXACTLY_ONCE)
    }

    return try {
        with(NullableBinding) { block() }
    } catch (ex: BindException) {
        null
    }
}

internal object BindException : Exception()

object NullableBinding {
    fun <V> V?.bind(): V = this ?: throw BindException
}
example:
Copy code
val a: Int? = 1
    val b: String? = "b"
    fun f(i: Int, ii: String) = i.toString() + ii
    val s = nullable {
        val aa = a.bind()
        val bb = b.bind()
        f(aa, bb)
    }

    val z = nullable {
        val a: Int = null.bind()
        val b: String = "S".bind()
        f(a,b)
    }

    println(s)
    println(z)

> 1b
> null
The purpose is saving nested let expressions when we want an expression to be be null if any one of a set variables on which it depends are null:
The issue with this is at the moment passing null to a non nullable argument is a compiler error. removing this could end up with a lot of bugs in codebases where people accidentally passing nullable values to non nullable arguments.
u
Even for a single argument there would be a use case. Symmetry. Kotlin has a simple expression to only call a function if the receiver is non-null. If you refactor from `A?.f()`to `f(a:A?)`you need to restructure all call sites from `a?.f()`to the much more complex
a?.let { f(it) }
which is quite a bit harder to read. Especially as naming the lambda parameter
a
will yield a warning
👍 1
😮 1
m
@kioba in order to have a "null coalescing function call" (or any "null coalescing expression"), you have to mark the argument with a question mark, in the example that I proposed.
Copy code
fun f(a: A, b: B): T = TODO()
fun g(a: A?, b: B?) {
    val x = f(a?, b?)
}
Note the
?
after
a
and
b
in the call to
f
. Otherwise the compiler would not generate the null-coalescing code and emit an error, as you are attempting to pass a nullable value to a non-nullable parameter.
@uli You can actually write
a?.let{ f(a) }
(only when
a
is not a
var
, though) but I find it silly and I'd prefer writing
f(a?)
, which would also be more general.
k
sorry I didn't see the
?
in the proposal. You are right this would eliminate the ambiguity.
m
By the way, "null-coalescing expression" is a misnomer. I would not know how to name this feature.
h
null-dissociating? \\/
e
Copy code
val x = run {
    f(a ?: return@run null, b ?: return@run null)
}