kotlinif-statementlambdatype-inferencekotlin-when

Can't use if/when assignment to return lambda with inferred parameter but can use if/when blocks


I have function returning a lambda based on an input String condition using if statement, which works fine - using this modified example from Head First Kotlin:

fun getConversionLambda(str: String): (Double) -> Double {
    if (str == "CelsiusToFahrenheit")
        return { it * 1.8 + 32 }
    if (str == "KgToPounds")
        return { it * 2.204623 }
    return { it }
}

But since that's an obvious good place to use when instead, and I use the <function declaration> = <expression> format, including the return type, then at compile or pre-compile time, I get an Unresolved reference: it error:

fun getConversionLambda2(str: String): (Double) -> Double = when(str) {
    "CelsiusToFahrenheit" -> { it * 1.8 + 32 }
    "KgToPounds" -> { it * 2.204623 }
    else -> { it }
}

Even if I specify it as the result of return within the function block, or assign it to a variable first and then return, I still get the Unresolved reference error:

fun getConversionLambda3(str: String): (Double) -> Double {
    return when (str) {
        "CelsiusToFahrenheit" -> { it * 1.8 + 32 }
        "KgToPounds" -> { it * 2.204623 }
        else -> { it }
    }
}

Only way I could get it to work is by specifying the lambda's input variable-type in the lambda:

// and infers the `(Double) -> Double` return type correctly if removed
fun getConversionLambda4(str: String): (Double) -> Double = when(str) {
    "CelsiusToFahrenheit" -> { x: Double -> x * 1.8 + 32 }
    "KgToPounds" -> { x: Double -> x * 2.204623 }
    else -> { x: Double -> x }
}

(my main:)

fun main(args: Array<String>) {
    println("getCL: ${getConversionLambda("KgToPounds")(2.5)}")
//    println("getCL2: ${getConversionLambda2("KgToPounds")(2.5)}")
//    println("getCL3: ${getConversionLambda3("KgToPounds")(2.5)}")
    println("getCL4: ${getConversionLambda4("KgToPounds")(2.5)}")
    
}

Why does the if version not have a problem with it? It's obviously inferring the lambdas' parameter type and doing the one-param-it based on getConversionLambda's definition's explicit return type. So why not for the when-version 2 & 3? I'm on Kotlin v1.4.32.

Edit: It seems any 'expression assignment' version of if & when prduces this issue unless I explicitly specify the parameter type for the lambda:

// Unresolved reference: it
fun getConversionLambda1A(str: String): (Double) -> Double =
    if (str == "KgToPounds") { it * 2.204623 } else { it }

// Unresolved reference: it
fun getConversionLambda1B(str: String): (Double) -> Double {
    return if (str == "KgToPounds") { it * 2.204623 } else { it }
}

But these two versions with the lambda parameter specified don't produce the error:

// works
fun getConversionLambda1Aokay(str: String) =
    if (str == "KgToPounds") { x: Double -> x * 2.204623 } else { x: Double -> x }

// works
fun getConversionLambda1Bokay(str: String): (Double) -> Double {
    return if (str == "KgToPounds") { x: Double -> x * 2.204623 } else { x: Double -> x }
}

Solution

  • The issue is that you're not within the scope of the passed in function when trying to reference "it". Just add braces and you're all set.

    fun getConversionLambda1A(str: String): (Double) -> Double =
    if (str == "KgToPounds") { { it * 2.204623 } } else { { it } }
    
    fun getConversionLambda2(str: String): (Double) -> Double = when(str) {
    "CelsiusToFahrenheit" -> {{ it * 1.8 + 32 }}
    "KgToPounds" -> {{ it * 2.204623 }}
    else -> {{ it }} }