Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to refer to an outer function from a lambda?

Tags:

kotlin

The question is in the comment. I want to refer to the outer function append, and not the one that's defined in the StringBuilder, how do I do this?

fun append(c: Char) {
    println("TEST")
}

fun sbTest() = with(StringBuilder()) {
    for(c in 'A'..'Z') {
        append(c) // how do I refer to the append function declared above?
    }
    toString()
}

I know I can introduce a function reference variable, like this:

val f = ::append

and call f instead of append, but is there another way?

like image 742
Coder-Man Avatar asked Mar 05 '23 12:03

Coder-Man


2 Answers

The problem is that anything called within with shadows the outer functions, because this is introduced. The same problem appears if you have a class and a top-level function with a function with the same signature.

The obvious option would just be re-naming it. Also, the function you have there isn't really descriptive compared to what it actually does. But if you for some reason can't rename, there are still other options.

Top-level methods can be referenced by package in Kotlin, for an instance like com.some.package.method. It can also be imported as such, which is the most common way to do it. There are very few methods that are called as com.some.package.method().

Kotlin, like Python, allows as in imports. Which means, you can do this:

package com.example;

// This is the important line; it declares the import with a custom name.
import com.example.append as appendChar; // Just an example name; this could be anything ***except append***. If it's append, it defeats the purpose

fun append(c: Char) {
    println("TEST")
}

fun sbTest() = with(StringBuilder()) {
    for(c in 'A'..'Z') {
        appendChar(c) 
    }
    toString()
}

Alternatively, as I mentioned, you can add the package:

for(c in 'A'..'Z') {
    com.example.append(c) 
}

val f = ::append is of course an option too, either way, it is still easier to rename the function than create imports with as or constants, assuming you have access to the function (and that it doesn't belong to a dependency).


If your file is outside a package, which I do not recommend you do, you can just declare the import as:

import append as appendChar
like image 76
Zoe stands with Ukraine Avatar answered Mar 14 '23 21:03

Zoe stands with Ukraine


You could also use an extension function instead of with(), such as .let{...}. This will send StringBuilder as an argument to the extension function as it (You can rename it to whatever you want btw):

fun sbTest() = StringBuilder().let{ it ->
    for(c in 'A'..'Z') {
        // using string builder
        it.append(c)
        // using your function
        append(c) 
    }
    it.toString()
}

The .let{...} function returns your last statement, aka the String from toString(), so your original function would still return it properly. Other functions can return this instead, such as .also{...}

I tend to use extension functions rather than with() as they're more flexible.

See this post to master extension functions: https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84

EDIT: Got also{} and let{} mixed up. I switched them

like image 41
P Fuster Avatar answered Mar 14 '23 23:03

P Fuster