[Solved] How to detect if a function parameter value was entered or it is the default value

Note: I posted the same question in StackOverflow with no luck

Having a function defined as

fun foo(
  bar: String? = null
) { ... }

how can be known if bar is null because no value was passed or because null was passed? I.e. the function was called as foo() or foo(bar = null)?

Actual case:

data class State(
  val foo: String? = null,
  val bar: Int? = null,
  val baz: Long? = null,
)

class StateManager {

  var state: State
    private set

  fun setState(
    foo: String? = null,
    bar: Int? = null,
    baz: Long? = null,
  ) {
    state = state.copy(foo, bar, baz)
  }
}

This example is not working, because setState(bar = 1) overrides with null foo and baz variables. state.copy(bar = 1) instead does not override foo and baz. I assume copy’s implementation is able to differentiate if null is provided as a parameter or if it is the default value.

The best I got:

  fun setState(
    val foo: String? = null,
    val bar: Int? = null,
    val baz: Long? = null,
  ) {
      val copyRef = state::copy
      val params = copyRef.parameters
      val values = mutableMapOf<KParameter, Any?>()
      if (foo != null) values[params[0]] = foo
      if (bar != null) values[params[1]] = bar
      if (baz != null) values[params[2]] = baz
      state = copyRef.callBy(values)
  }

This solution does not override with null unspecified values, but is not able to assign null when wanted.

There is no need for copy method to differentiante between parameters.
Actuall you can create a copy method yourself rather easily:

class Example(val a:Int?, val b:String?, val c:Boolean?)

fun Example.copy(a: Int? = a, b: String? = b, c: Boolean? = c) = 
   Example(a,b,c)

As you can see, copy doesn’t need null or any other value as a default values, because it usses current values.

Hi Miki, thanks for your answer. In my case, Example is a complex object with lots of members. I want to be able to:

  1. Only pass to copy the params I want to change. For example: copy(b = "hello!")
  2. Be able to put a property to its default value. For example: copy(b = null)

The solution I provided which uses reflection gets 1 but not 2.

In your specific case, is anything preventing you for setting state directly?

data class State(
  val foo: String? = null,
  val bar: Int? = null,
  val baz: Long? = null,
)

class StateManager {

  var state: State

}

And if you find it cubersome to write in everyplace:

stateManager.state = stateManager.state.copy(...)

You can add nice little helper method, something like this:

fun StateManager.updateState(
    foo: String? = state.foo,
    bar: Int? = state.bar,
    baz: Long? = state.baz,
){
   state = State(foo, bar, baz)
}

You can’t.
It seems to fundametally defy the concept of default arguments.

It’s not, nor null is in general a valid argument (what if the field itself is not nullable?)

You can see the definition from the documentation:

it simply has the defaults initialized to the current object fields.

@MikibeMiki @al3c

it simply has the defaults initialized to the current object fields

That’s the key I was looking for, thank you very much! It seems so obvious now you said me :sweat_smile:

1 Like

Just to answer the original question, you can’t know if a function call is using a default value. When you call setState(bar = 1) the compiler basically changes the call to setState(null, 1, null).