6

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 
    }
}
Neo
  • 3,465
  • 5
  • 26
  • 37

2 Answers2

3

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.

Roland
  • 22,259
  • 4
  • 57
  • 84
  • I tried that, but then it doesn't allow me to pass A. Check update for details. – Neo Apr 08 '19 at 11:38
  • @Neo updated the answer... basically showing how you can solve your updated sample with a generic function on a generic receiver... – Roland Apr 08 '19 at 12:26
  • 1
    Yes, this is exactly what I was looking for! Neat approach. – Neo Apr 10 '19 at 05:55
  • 1
    To help others looking at this: The important difference between the code I posted in my update and @Roland's answer is the "T.g" part, which allows the function to be an extension function for the generic type T. – Neo Apr 10 '19 at 06:02
3

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.

TheOperator
  • 5,936
  • 29
  • 42
  • Great answer — but is there a way to avoid any potential confusion between _in variance_ and _invariance_?  (E.g. _‘in’ variance_?) – gidds Apr 08 '19 at 14:27
  • I think better terms (also known in other languages) are _covariance/contravariance_ (see [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science))). Kotlin talks about _in/out_ in the context of [variance](https://kotlinlang.org/docs/reference/generics.html#variance), but not explicitly "in variance/out variance". I can edit the post accordingly. – TheOperator Apr 08 '19 at 14:35
  • Even better!  (Though there's now a typo in the last paragraph which rather spoils the effect… :) – gidds Apr 08 '19 at 14:48
  • And of course certain usages prevent either type of variance. – Paul Stelian Jun 24 '21 at 23:01