Is there a way to distinguish between a function argument's default value having been passed explicitly or implicitly in Kotlin?
Asked Answered
O

2

5

Assuming a kotlin function like this:

fun f(p1: T1? = null, p2: T2? = null, ..., pN: TN? = null) {
    // ...
}

Can the above function's implementation distinguish between the following two calls, where the first one passed p1 = null implicitly, and the second one passed it explicitly?

f()          // Implicit
f(null)      // Explicit
f(p1 = null) // Explicit

Note: There could be arbitrary numbers of parameters

Operand answered 12/5, 2020 at 20:51 Comment(0)
B
5

No, it cannot distinguish between those cases.

You could distinguish between them if you added a distinct overload, however.

Barkley answered 12/5, 2020 at 21:5 Comment(1)
In the real world case, I have N parameters, so overloading won't work...Operand
L
1

Although I'd rather not use that approach in production, you could do something like I've done in the following snippet:

object Default {
    val defaultMapping = mutableMapOf<KClass<*>, Any?>()

    inline fun <reified T> get(): T? =
            T::class.let {
                defaultMapping[it] ?: it.java.constructors.getOrNull(0)?.let { c ->
                    try {
                        // NOTE: for now only parameterles constructor will work
                        c.newInstance()
                    } catch (e: Exception) {
                        e.printStackTrace()
                        null
                    }.also { v ->
                        defaultMapping[it] = v
                    }
                } ?: run {
                    defaultMapping[it] = null
                    null
                }
            } as? T

    inline fun <reified T> T.isDefault(): Boolean = defaultMapping[T::class] == this
}

inline fun <reified T> foo(bar: T? = Default.get()) {
    if (bar?.isDefault() == true) println("bar: default is in use")
    else println("bar: $bar")
}

fun main() {
    foo<Any>()
    foo(Default.get<Any>())
    foo<Any>(null)
    foo<Any>(bar = null)
    foo(Any())
    val a = Any()
    foo(a)
    foo(bar = a)
}

Note, that I have not polished the code in any way. Some parts are leftovers from several attempts (e.g. the part about the constructors.getOrNull(0)) and I don't intend to improve that.

Also: This simple approach only works with default constructors (see it.newInstance()) on the JVM. So that's no multi-platform solution in any way.

The result is something like

bar: default is in use
bar: default is in use
bar: null
bar: null
bar: java.lang.Object@41906a77
bar: java.lang.Object@4b9af9a9
bar: java.lang.Object@4b9af9a9

Again: Keep in mind, this is very simplistic, don't use that in production!

Lagena answered 13/5, 2020 at 12:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.