Neo
Neo

Reputation: 3705

Kotlin higher order function parameters: Passing subtypes

I have run into a problem with function parameters in Kotlin. I will explain the issue with the help of some code.

I created a class hierarchy. When I pass a subtype into a function expecting a parent type, there is no issue.

open class A (val i: Int)
class B (val j: Int) : A(j)

fun f(x: A){
    print(x)
}

fun test_f(){
    f(A(1))
    f(B(1)) //no problem
}

I tried to mimic this with function parameters.

fun g(x: (A)->Int){
    print(x)
}

fun test_g(){
    val l1 = { a: A -> a.hashCode()}
    g(l1)

    val l2 = { b: B -> b.hashCode()}
    g(l2) //Error: Type mismatch. Required: (A)->Int, Found: (B)->Int
}

It seems that function type (B) -> Int is not a subtype of (A) -> Int. What is the best way to address this?

My original problem is to define a higher order function in A.h that takes a function z: (A) -> X as parameter. And I want call h on an object of type B and pass a function z: (B) -> X.

Update: I tried generics with upper bound, yet my issue is not solved. Please find code below:

// Using generics doesn't allow me to pass A.
open class A (val i: Int) {
    fun <M: A> g(x: (M)->Int){
        print(x(this)) // Error: Type mismatch. Expected: M, Found: A 
    }
}

Upvotes: 6

Views: 1427

Answers (2)

TheOperator
TheOperator

Reputation: 6476

What you're trying to do is a conversion from function type (B) -> Int (source) to (A) -> Int (target). This is not a safe conversion.

Your source function (B) -> Int takes any instance which is a B, but not necessarily an instance of type A. More concretely, it cannot handle all arguments that are of type A but not of type B.

Imagine your classes look like this:

open class A
class B : A {
    fun onlyB() = 29
}

You can define a function

val fb: (B) -> Int = { it.onlyB() }
val fa: (A) -> Int = fb // ERROR

The function fb will not be able to operate on class A, since A does not have the onlyB() function. As a consequence, you're not allowed to convert it to a function type which takes A parameters.


This concept is called contravariance, meaning that input parameters can only be constrained by becoming more concrete, not more abstract. So, the opposite direction works:

val fa: (A) -> Int = { it.hashCode() }
val fb: (B) -> Int = fa // OK, every B is also an A

In contrast, for return values, the concept of covariance applies. This means that return values are allowed to become more abstract, but not more concrete:

val fInt: (B) -> Int = { it.onlyB() }
val fNum: (B) -> Number = fInt // OK, every Int is also a Number

These relations can be exploited in generic classes, using Kotlin's in (contravariance) and out (covariance) keywords -- see here for detailed explanation.

Upvotes: 3

Roland
Roland

Reputation: 23262

You can solve it using generics and an extension function on a generic receiver. Deriving the extension function from your updated sample:

fun <T : A> T.g(x: (T)->Int){
    print(x(this))
}

This way it is ensured that the receiver and the first parameter type of the given function are the same, which is either an A or a subtype of it.

Upvotes: 3

Related Questions