Kotlin Function Literal with Any Receiver type - kotlin

I'm trying to make kotlin accept an interface type as a receiver for a function but I get a compile error saying type mismatch.
Here is an example:
interface Foo
class Bar : Foo
fun main() {
val l = initBar {
}
initFoo(l) // Doesnt work compile error: Type mismatch.
//Required:
// Foo.() → Unit
//Found:
// Bar.() → Unit
}
fun initBar(init: Bar.() -> Unit) = init
fun initFoo(init: Foo.() -> Unit) = Unit
I get the following compile error from this code
Type mismatch.
Required:
Foo.() → Unit
Found:
Bar.() → Unit
Is there any way to make the initFoo function accept any reciever object that implements Foo without casting?
Thanks.

Use a generic type for it:
fun <T: Foo> initFoo(init: T.() -> Unit) = Unit
Note this limits what you can do with it inside the function. Since you accept a function that could be called on a specific type, you can't call the passed-in lambda on any Foo.
fun <T: Foo> initFoo(init: T.() -> Unit) {
val someFoo = object: Foo {}
someFoo.init() // ERROR (type mismatch)
}
This is because, for instance, your Bar.() -> Unit function could be using properties and functions of Bar that are not necessarily part of any arbitrary Foo.

Related

Unresolved reference after changing lambda to method reference

In the following example (also on Kotlin Playground), IntelliJ offers to change the lambda (as in the commented-out first line of main) to a method reference (as shown in the second line of main).
fun Int.matches1(n: Int): Boolean = TODO()
fun Int.matches2(n: Int): Boolean = TODO()
fun main() {
// val matches: Int.(Int) -> Boolean = { n -> matches1(n) }
val matches: Int.(Int) -> Boolean = ::matches1
}
The line containing the lambda works fine. However, if I try to use the method reference, the Kotlin compiler rejects it with Unresolved reference: matches1. Why?
You need to specify the receiver type, since you're trying to reference an extension function. It's similar to when you want to reference a function that belongs to a class. So this works:
val matches: Int.(Int) -> Boolean = Int::matches1

Why is this Kotlin class property not shadowed by a method parameter?

Looking at this code in kotlinx.coroutines, I noticed something strange:
/**
* Returns a flow containing the results of applying the given [transform] function to each value of the original flow.
*/
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return#transform emit(transform(value))
}
In the first line, the transform used is clearly this.transform (defined here). Shouldn't the transform declared in the method parameter have been used instead, as it is in the second line?
To test this, I wrote a small class which tries to mimc this behaviour:
// flow.kt
class Flow(val name: String) {
public fun transform (transform: (Any) -> Unit): Flow {
return Flow("transformed")
}
public fun emit(value: Any) {
// do nothing
}
public fun map(transform: (Any) -> Unit): Flow = transform { value ->
return#transform(emit(transform(value)))
}
}
And I get the kind of warning I was expecting when I run kotlinc flow.kt:
flow.kt:12:54: error: type mismatch: inferred type is Unit but Flow was expected
public fun map(transform: (Any) -> Unit): Flow = transform { value ->
^
flow.kt:12:66: error: cannot infer a type for this parameter. Please specify it explicitly.
public fun map(transform: (Any) -> Unit): Flow = transform { value ->
^
(Kotlin version as returned by kotlinc -version is "kotlinc-jvm 1.6.10 (JRE 17.0.1+1)")
So why is it that the code defined in kotlinx.coroutines works? If I understand Kotlin's name shadowing rules correctly it shouldn't have.
In kotlinx.couroutines, the transform parameter takes an argument of type T. Hence, this.transform is used when transform is called with a (Any) -> Unit argument.
In your example, the transform parameter takes an argument of type Any. A (Any) -> Unit is an Any and hence the parameter is being used instead of this.transform. Replacing Any with a type parameter will make your code compile too.

How to impose generic constraints to the parameters and return values of a function argument of a generic Kotlin function?

I want to define a Kotlin generic function that takes a lambda as a parameter, but I want to restrict the allowed types of lambdas parameters and return types. How do I do this in Kotlin?
In the following example, I expected the constraint: where T: Base, V: (T) -> Unit to mean that V can only be functions whose first parameter implements Base.
However, I see that the compiler ignores the T: Base part of the constraint and will accept any (Any) -> Unit.
interface Base
fun <T, V> exampleGenericFunction(func: V) where T: Base, V: (T) -> Unit {
println("func is $func")
}
class ImplementsBase : Base
class DoesNotImplementBase
fun main() {
val f1: (ImplementsBase) -> Unit = { }
exampleGenericFunction(f1)
val f2: (DoesNotImplementBase) -> Unit = { }
exampleGenericFunction(f2) // expected this to be a compilation error
}
This unexpected behavior was caused by a bug in the Kotlin >=1.4 compiler.
This bug can be tracked here: https://youtrack.jetbrains.com/issue/KT-48935.

Function overload ambiguous with type covariance but not ambiguous with generic

Suppose I have the following code.
open class Parent
class Child: Parent()
fun <T: Parent> foo(obj: T): T = obj
inline fun <T: Parent> foo(block: () -> T): T = foo(block())
fun <T> bar(obj: T): T = obj
inline fun <T> bar(block: () -> T): T = bar(block())
fun main() {
/* Compile error:
Overload resolution ambiguity. All these functions match.
public inline fun <T : Parent> foo (block: () → TypeVariable(T)): TypeVariable(T) defined in examples in file example.kt
public fun <T : Parent> foo (obj: TypeVariable(T)): TypeVariable(T) defined in examples in file example.kt
*/
foo { Child() }
// Works
bar { "something" }
}
The first function call (foo) gives me a compilation error saying the overload resolution is ambiguous. But why is this true since the block is of type () -> T and this isn't a subtype of Parent?
Shouldn't this error occur on the second function call (bar)? Why doesn't it occur?
My guess is that the problem here is with type inference. In each of your examples, you're relying on type inference for two things:
To determine the value of the generic type parameter T, and
To determine the signature of the lambda function.
In the case of foo, the overload resolution depends on knowing the value of the generic parameter T. That's because T is bounded. The actual value of T might affect whether it's valid to call this method or not, depending on whether T turns out to be a subtype of Parent.
The problem is that T needs to be inferred based on the value of the parameter being passed in, and the type of the parameter being passed in needs to be inferred based on some information about which overload has been selected and/or what the value of T is! I suspect this creates a circular dependency that prevents the overload resolution from completing.
You can fix it by providing a value either for the lambda signature or for the generic type parameter:
foo<Parent> { Child() } // works
foo({ Child() } as () -> Parent) // works
Extracting the lambda to a variable fixes it too, because it makes the compiler infer a type for the variable straight away:
val lambda = { Child() } // type is inferred as () -> Child
foo(lambda) // works, because lambda already has an inferred type
The problem doesn't arise with bar because in that case, T is unbounded. No matter what T ends up being, it won't affect whether that particular overload is allowed to be called or not. That means the compiler doesn't need to infer a value for T before performing overload resolution.
Looks like a bug(s) in the compiler (still present in 1.6.0-M1).
Two workarounds here:
Explicitly specify type argument:
foo<Child> { Child() }
Ironically, doing the same with the working part will break it (but the error won't be about ambiguity, the compiler is pretty sure what overload he's gonna call - the wrong one):
/* Compile error:
Type mismatch.
Required: () → String
Found: String
*/
bar<() -> String>({ "something" })
Extract lambda into a separate variable:
val lambda = { Child() }
foo(lambda)
This will fix the broken part as well:
val block = { "something" }
bar<() -> String>(block)

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.
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.
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)
}
}