4

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 }
}
aneroid
  • 12,983
  • 3
  • 36
  • 66
  • 1
    That's really strange. What makes it even stranger is that you don't even have to declare the type where you've written `{ x: Double -> x }`, `{x -> x}` works just fine, so it's not like it has an issue inferring that it's a double. – Henry Twist May 04 '21 at 21:52
  • 1
    @HenryTwist If I remove the return-type specification, that version (4) works correctly. But the lambdas still need the input parameter specification: `{ x: Double -> ... }` – aneroid May 04 '21 at 21:55
  • 1
    Sorry yes, I missed that you were inferring the return type. My comment is relevant when it's specified. – Henry Twist May 04 '21 at 21:57
  • 1
    Very interesting!  Seems that the expression body isn't the problem, nor using a `when` as such — the problem comes when you make the `when` into an expression by replacing the individual `return`s with one before the `when`.  But I've no idea why that causes it to fail… – gidds May 04 '21 at 22:50
  • 2
    (And I can't resist some pointless pedantry: ‘Fahrenheit’ has another ‘h’ in it; ‘Celsius’ is preferred instead of ‘Centigrade’ as the latter has another meaning; and ‘Kg’ isn't usually pluralised.) – gidds May 04 '21 at 22:55
  • @gidds Haha, nice catch. I don't usually utter or use the word 'Fahrenheit' since we do it by Celsius. Indeed, 'Centigrade' sounded off since we usually say Celsius. Bad typos when writing code from the book. (Just went down a 'Centigrade vs Celsius' rabbit hole.) Thanks! :-) The 'Kgs' made me shudder, like when I see 'kph' instead of 'kmph' or 'km/h'. I should've fixed it. – aneroid May 04 '21 at 23:04
  • @gidds Now edited, with correct spellings and standard notation. 'Kg' is still wrong (should be 'kg') but will leave it closer to the book version. – aneroid May 04 '21 at 23:22
  • 1
    Thank you for taking the time to post this question! I too did this—seeing that `return when` was idiomatic and far clearer—and had the same issue! – Bink Jul 01 '22 at 23:12

1 Answers1

2

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 }} }
ruffCode
  • 271
  • 1
  • 4
  • Okay, and why is the scope not a problem in the regular block version? Btw, I'm not _passing in_ a function, I'm trying to _return_ one. – aneroid May 05 '21 at 02:55
  • In the first example, you are explicitly returning a function by calling return { } while a when expression returns the value after -> or if using braces, the last value between them. So -> 5 and -> { 5 } both return 5 while -> { { 5 } } returns a function. And I'm sorry I misspoke. The issue was that at that point the return value was not a function which is why it was undefined – ruffCode May 05 '21 at 03:25
  • aaah... now that makes sense. So you're saying that **the first set of curly braces is treated as a code-block for that when condition's result** so a 2nd set of curly braces is needed to denote the lambda. And the same thing happens for the `if` assignment - the last statement of the block is treated as the return value and the first set of `{ }` denote a block, so another set is needed to indicate the lambda. – aneroid May 05 '21 at 03:54
  • Additionally, when I have the `if/when` returning `{ x: Double -> ... }` or `{ x -> x...}` that tells the compiler that it's the parameter indicator for a lambda, so it figures out that it's a lambda (single expression) being returned and the `{}` aren't for a code block. That also explains why the auto-formatter was making the previous case formatted as "block" instead of as the standard lambda format with the curly's on the same line with a space. – aneroid May 05 '21 at 04:08
  • That's correct. If you needed to return a function with more than one parameter, say (Double, Double) you'd be limited to { x, y -> x * y } as { { it } } would not work – ruffCode May 05 '21 at 04:14