Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 349
Lukas Eder Avatar asked May 12 '20 20:05

Lukas Eder


People also ask

When overriding methods with default parameter values you must specify a new default value for the parameters?

A default value is defined using = after the type. Overriding methods always use the same default parameter values as the base method. When overriding a method that has default parameter values, the default parameter values must be omitted from the signature: open class A { open fun foo(i: Int = 10) { /*...

How do you pass a default parameter in Kotlin?

Kotlin Default Argument In Kotlin, you can provide default values to parameters in function definition. If the function is called with arguments passed, those arguments are used as parameters. However, if the function is called without passing argument(s), default arguments are used.

Is Kotlin pass by value?

Since Kotlin is a new language for JVM, like Java it is pass-by-value.

What is Vararg in Kotlin?

In Kotlin, You can pass a variable number of arguments to a function by declaring the function with a vararg parameter. a vararg parameter of type T is internally represented as an array of type T ( Array<T> ) inside the function body.


2 Answers

No, it cannot distinguish between those cases.

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

like image 111
Louis Wasserman Avatar answered Oct 16 '22 20:10

Louis Wasserman


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!

like image 33
dzim Avatar answered Oct 16 '22 20:10

dzim