Some lambda improvements

  1. Inline casting

In Android development using Kotlin, a very used method is the setOnClickListener from the View class, which is defined as fun setOnClickListener(action: (View) -> Unit). So if one wants to add a click listener to a Button (which is a subclass of View), for example, it’d be:

val button = findViewById<Button>(R.id.btn_id)
button.setOnClickListener { view ->
    // argument `view` contains a `View` instance
}

A View does not contain a setText method, but a Button does, so the following is illegal:

button.setOnClickListener { view ->
    view.setText = "Clicked"    // Unresolved reference: setText
}

To be legal, one would have to do:

button.setOnClickListener { view ->
    view as Button
    view.setText = "Clicked"
} 

So I propose, for readability and for simplicity (since casting more than one lambda argument would increase the LOC), the following syntax:

button.setOnClickListener { (view as Button) ->
    view.setText = "Clicked"    // valid
}
  1. Argument inference

Using the same example as above, let’s suppose I have a function that I would like to use to be a button click listener:

fun onClick() {
    println("Button clicked")
}

Passing this function in the setOnClickListener method of the Button, we’d have:

button.setOnClickListener { view ->
    onClick()
    // notice that we don't use the `view` lambda argument
}

But if we use the member reference operator (::), we get an error:

button.setOnClickListener(::onClick)
// Type mismatch: inferred type is KFunction0<Unit> but (View) -> Unit was expected

So I propose the following: if a function receives as argument an object KFunctionN<T> and an object KFunction0<T> is passed as argument to this function, this is a valid statement (where N is the number of arguments and T is any type). In another words, if a function f receives a function fA as argument, and a function fP contains the same return type as fA but with no arguments, fP can be passed as argument to f.

We can also expand this to other situations, such as:

fun f(fA: (Int, String, Double) -> Int) {
    println(fA(1, "2", 3.0))
}

fun fP0(a: Int, b: String): Int = a + b.toInt()
// calling f(::fP0) is legal

fun fP1(a: Int, c: Double): Int = (a * c).toInt()
// calling f(::fP1) is illegal

fun fP2(a: Int, b: String): String = "$a$b"
// calling f(::fP2) is illegal

What do you think?

This solution merges header with body.
It is shorter, but this does not imply readable.

Who expects a runtime exception in a declaration?

1 Like

OR, you can use a feature that’s already available, which is type parameters. The following code should work and provide you with the safety that you need:

inline fun <reified V: View> V.onClickListener(crossinline listener: (V) -> Unit) {
    setOnClickListener { 
        listener(it as V)
    }
}

Usage:

button.onClickListener { btn ->
    btn.setText("Clicked") // valid
}
1 Like

This I can definitely get behind. We already have some magic with extension functions being able to be used as a regular function with the reciever as the first argument, so why shouldn’t we also allow this? Tbh I personally cannot really see anyway that this can break existing code or be not understandable. It’s just basically saying “I don’t care at all about the passed in parameters, but I’ll still return you the value that you want!”, which seems really valid. Just one small comment, you cna also currently do this:

button.setOnClickListener { onClick() }

which is maybe slightly more verbose, but tbh I’m kinda fine with it for now, especially because it’s literally the same number of characters as the proposed solution (and yes, ik that number of characters is definetly not a good measurement. What I’m trying to convey is that it’s quite similar in a lot of ways ((...) vs {...} and ::onClick vs onClick()))
However, for any function that needs more that one argument, you get a lot more verbosity because you have to explicitly ignore all of them (i.e. by doing this functionCall { _, _, _, _ -> anotherFunctionCall() }), so I defintely do agree that this will help a lot with them.