Function with multiple lambda arguments vs. one interface & object expression

Kotlin has nice syntax for functions with one trailing lambda argument. But in some cases I’ve noticed how I ended up adding multiple lambda arguments to a function, and this seems like code smell to me, also because there’s no dedicated syntax for multiple lambdas, indicating to me that that case was not considered desirable/okay.

Is there a legitimate case where such multiple lambda arguments are necessary? Or is there a rule of thumb that if you need to pass more than one lambda you should define an interface & use an object expression instead? Which option is more “idiomatic” in Kotlin?

I don’t think multiple lambdas is necessarily bad, nor that Kotlin’s designers were trying to deliberately discourage it.

I just think that in practice the need for them is sufficiently rare that it wasn’t worth coming up with a special syntax for them.⠀(I can’t recall ever using them myself.)

Especially as the existing syntax isn’t particularly unwieldy:

function({ /*lambda1*/ }, { /*lambda2*/ })

(In practice you’d probably want to use line-breaks to separate them, of course.)

And don’t forget that you can give a default value for a function parameter, just like any other parameter.⠀So you might not need to provide more than one lambda, even when you can.⠀(Though remember that when outside parens, a lambda is taken as the last argument, so if you have two params with the same function type, take care!)

5 Likes

I don’t think that is the case. The way I see it functions that take a single lambda argument are just so common that we got this nice syntax. That does not mean that functions taking muliple lambdas are bad or wrong.

Not really. I personally can’t remeber many usecases for this so I don’t know which is more common. I guess it depends. If you are writing a libary that you expect people to use from java (as well as kotlin) going with interfaces is probably a good idea just to make your API easier to use from java.
On the other hand you might want to use inline functions in which case you have to go with multiple lambdas. I’d probably say that using multiple lambda parameters is the kotlin style of doing this but I would not enforce this for libaries since they need to consider java interop.

2 Likes

Depending on the use-case and if you really want to, you could define a mini-dsl. Something along the lines of:

class Context {
    internal var action: () -> Unit = {}
    internal var onSuccess: () -> Unit = {}
    internal var onError: (Exception) -> Unit = {}

    fun action(block: () -> Unit) { action = block }
    fun onSuccess(block: () -> Unit) { onSuccess = block }
    fun onError(block: (Exception) -> Unit) { onError = block }
}

fun runAction(block: Context.() -> Unit) {
    val context = Context().apply(block)
    try {
        context.action()
        context.onSuccess()
    } catch (ex: Exception) {
        context.onError(ex)
    }
}

fun main() {
    runAction {
        action {
            println("Run Action")
            throw RuntimeException("Failed")
        }
        onSuccess {
            println("Isn't called")
        }
        onError {
            println("Handle ${it.message}")
        }
    }
}
2 Likes

That’s really neat. I might consider this in some situations, though it is overkill in others. Thanks!

in android codebase at my company we had a common popup-dialog builder. we ended up with this:

fun showPopup(text: String, confirmation: String = "ok", 
  onConfirmationClicked: ()->Unit = {}, 
  onClickOutside: ()->Unit = {}
)

and then calls used named-arguments:

showPopup("you can't undo, continue?", 
  confirmation="DELETE", 
  onConfirmationClicked = { delete() }, 
  onClickOutside = { cancelOperation() }
)
2 Likes