Need help with Generics, contravariance needed

Sorry that I’m giving a longer example, but I think it becomes clearer what I want to do:

fun interface Pattern<in A> {
    fun test(obj: A): Boolean
}

class Matcher<A, R>(private val obj: A) {
    private var result: R? = null

    fun otherwise(default: () -> R) = result ?: default()

    infix fun Pattern<A>.then(value: () -> R) {
        if (result == null && this.test(obj)) {
            result = value()
        }
    }
}

fun <A, R> match(obj: A, body: Matcher<A, R>.() -> R): R =
    Matcher<A, R>(obj).run(body)

fun even() = Pattern<Int> { it % 2 == 0 }
fun <A> notNull() = Pattern<A> { it != null }

fun <A, B> pair(a: Pattern<A>, b: Pattern<B>) = Pattern<Pair<A, B>> {
    a.test(it.first) && b.test(it.second)
}
fun <A, B> pair(a: A, b: B) = Pattern<Pair<A, B>> {
    a == it.first && b == it.second
}

fun main() {
    val obj: Pair<String?, Int> = "Sam" to 24
    val result = match(obj) {
        pair(notNull<String?>(), even()) then { "yes" }
        pair("x", 14) then { "oops" }  //doesn't compile
        otherwise { "no" }
    }
}

So, I have two problems here, maybe related: First one is how to get proper type inference in case of notNull(), so that I don’t need to specify the type (here String?) explicitly? And the second, more important one is that the second line with then doesn’t compile, because the required type is Pair<String?, Int>, but the type for the given pattern is apparently inferred as Pair<String, Int>. I think it boils down to the line infix fun Pattern<A>.then(value: () -> R), where I need instead of A something like “supertype of A”, but I don’t know how to express this.

I’ll have to read the details later but just based on your comment, have you considered variance?

List<Sting?> is not a super class to List<String>.
Very common mistake to thing Foo<MySuperType> is somehow a super to Foo<MySubtype> unless you are explicitly about variance

If I understand your case correctly then:

1. In your case it is not possible to infer what is the type of the pattern returned from notNull(). Well, technically speaking, we could use subsequent then to infer what should be the type of the pattern and then use it to infer the return type of notNull(). Kotlin inference is not so advanced.

But because your notNull() can be used against any object, I think you can replace it with:

fun notNull() = Pattern<Any?> { it != null }

2. I’m not exactly sure why do you expect this code to compile fine. You have a Pattern which can only test String and you would like to use it for testing String? which it doesn’t support. You said you would like to receive a supertype of A in then(), then what would be your expected behavior if we use e.g. your even() pattern to test something which is not Int?

If you meant that technically speaking your pair() implementation can test against nullable types, then I guess your problem is not with then, but with the type of the Pattern returned from pair(). You can either do this:

pair("x" as String?, 14) then { "oops" }

or this:

fun <A, B> pair(a: A, b: B) = Pattern<Pair<A?, B?>> {

Whatever makes more sense to you.