Trick to get type inference work here?

I’m working on a pattern matching library (Kopama), and would like to avoid specifying the type in a certain case. I simplified the example as much as I could:

class Matcher<P>(val obj: P) {
    var result: String? = null
    inline infix fun <reified Q : P> ((Q) -> Boolean).then(value: String) {
        if (result == null && obj is Q && this(obj)) {
            result = value
        }
    }
}

fun <P> match(obj: P, body: Matcher<P>.() -> Unit): String? =
    Matcher<P>(obj).also(body).result

fun <A, B> pair(fst: (A) -> Boolean, snd: (B) -> Boolean) = { p: Pair<A, B> ->
    fst(p.first) && snd(p.second)
}

fun main() {
    val p = 6 to "xyz"
    val s = match(p) {
        pair<Int, String>({ it % 2 == 1 }, { it.length == 4 }) then "boo"
        pair<Int, String>({ it % 2 == 0 }, { it.length == 3 }) then "great"
    }
    println(s) // "great"
}

I would like to get rid of the <Int, String>. I couldn’t find a way to do it, and there might be no solution for this at all, but maybe I overlooked something…

1 Like

I’m not sure I see the full picture, but is there any reason pair wasn’t defined as an extension over the Matcher?

fun <A, B> Matcher<Pair<A, B>>.pair(fst: (A) -> Boolean, snd: (B) -> Boolean) = { p: Pair<A, B> ->
    fst(p.first) && snd(p.second)
}

fun main() {
    val p = 6 to "xyz"
    val s = match(p) {
        pair({ it % 2 == 1 }, { it.length == 4 }) then "boo"
        pair({ it % 2 == 0 }, { it.length == 3 }) then "great"
    }
    println(s) // "great"
}
2 Likes

That would only work for top-level calls, but it would prevent pair from being useful in “inner” positions. It’s still an interesting thought, because there could be both versions with slightly different names:

fun <A, B> Matcher<Pair<A, B>>.pair(fst: (A) -> Boolean, snd: (B) -> Boolean) = { p: Pair<A, B> ->
    fst(p.first) && snd(p.second)
}

fun <A, B> pair_(fst: (A) -> Boolean, snd: (B) -> Boolean) = { p: Pair<A, B> ->
    fst(p.first) && snd(p.second)
}

fun main() {
    val p = 6 to (1.0 to "xyz")
    val s = match(p) {
        pair({ it % 2 == 1 }, pair_({ it > 2.0 }, { true })) then "boo"
        pair({ it % 2 == 0 }, pair_({ it < 2.0 }, { true })) then "great"
    }
    println(s) // "great"
} 

That would be certainly better than having to type generics over and over again (and as the library also generates similar functions for data classes, there could be potentially many generics in play).

There might be the problem of inheritance - not in case of Pair, but for other types, when the pattern is not the type of the match block, but a subtype. But then you could still use the original non-extension version with generics as a “fallback”.

Thanks for the idea!