Aaron Christiansen
Aaron Christiansen

Reputation: 11807

Kotlin "expected no parameters" when attempting to return inline lambda

I'm trying to write a Kotlin function which returns a lambda taking a parameter. I'm attempting to use code like the following to do this:

fun <T> makeFunc() : (T.() -> Unit) {
    return { t: T ->
        print("Foo")
    }
}

Note: In the actual program, the function is more complex and uses t.

Kotlin rejects this as invalid, giving an 'Expected no parameters' error at t: T.

However, assigning this lambda to a variable first is not rejected and works fine:

fun <T> makeFunc() : (T.() -> Unit) {
    val x = { t: T ->
        print("Foo")
    }

    return x
}

These two snippets seem identical, so why is this the case? Are curly braces after a return statement interpreted as something other than a lambda?

Additionally, IntelliJ tells me that the variable's value can be inlined, whereas this appears to cause the error.

enter image description here

Upvotes: 4

Views: 1536

Answers (2)

hotkey
hotkey

Reputation: 147971

There is a curious moment in the design of functional types and lambda expressions in Kotlin.

In fact, the behavior can be described in these two statements:

  • Named values of functional types are interchangeable between the ordinary functional type like (A, B) -> C and the corresponding type of function with the first parameter turned into receiver A.(B) -> C. These types are assignable from each other.

    So, when you declare a variable that is typed as (T) -> Unit, you can pass it or use it where T.() -> Unit is expected, and vice versa.

  • Lambda expressions, however, cannot be used in such free manner.

    When a function with receiver T.() -> Unit is expected, you cannot place a lambda with a parameter of T in that position, the lambda should exactly match the signature, a receiver and the first parameter cannot be converted into each other:

    Shape of a function literal argument or a function expression must exactly match the extension-ness of the corresponding parameter. You can't pass an extension function literal or an extension function expression where a function is expected and vice versa. If you really want to do that, change the shape, assign literal to a variable or use the as operator.

    (from the document linked above)

    This rule makes lambdas easier to read: they always match the expected type. For instance, there's no ambiguity between a lambda with receiver and a lambda with implicit it that is simply unused.

Compare:

fun foo(bar: (A) -> B) = Unit
fun baz(qux: A.() -> B) = Unit

val f: (A) -> B = { TODO() }
val g: A.() -> B = { TODO() }

foo(f) // OK
foo(g) // OK
baz(f) // OK
baz(g) // OK

// But: 

foo { a: A -> println(a); TODO() } // OK
foo { println(this@foo); TODO() } // Error

baz { println(this@baz); TODO() } // OK
baz { a: A -> println(a); TODO() } // Error

Basically, it's the IDE diagnostic that is wrong here. Please report it as a bug to the Kotlin issue tracker.

Upvotes: 5

s1m0nw1
s1m0nw1

Reputation: 81929

You are defining a function type () -> Unit on receiver T, there really isn't a parameter to that function, see "()". The error makes sense. Since you define the function type with T as its receiver, you can refer to T by this:

fun <T> makeFunc(): (T.() -> Unit) {
    return {
       print(this)
    }
}

Upvotes: 1

Related Questions