I’m working on a little pattern matching library called kopama. So far, I use KSP to allow the decomposition of data classes, e.g. for this code
@Kopama
data class Person(val firstName: String, val lastName: String, val age: Int)
there would be a function generated that takes sub-patterns for the fields and returns a pattern for the whole class (a pattern is just a “test function” (T) -> Bool
) :
fun person(
firstName: Pattern<String> = any,
lastName: Pattern<String> = any,
age: Pattern<Int> = any
): Pattern<Person?> = {
when(it) {
null -> false
else -> firstName(it.firstName) &&
lastName(it.lastName) &&
age(it.age)
}
}
This can be now used in expressions like person(startsWith("Dan"), any, gt(18))
, where the arguments are the mentioned sub-patterns
Now I found a way to do it without KSP, but via interfaces, and I would like to get your input before doing such a drastic change. First some preparations:
data class T3<A, B, C>(val a: A, val b: B, val c: C)
private fun Any.getComp(n: Int): Any? =
this::class.members.filter {
it.name == "component$n" && it.parameters.size == 1
}.first().call(this)
interface Unapply3<A, B, C> {
fun unapply(): T3<A, B, C> = T3(getComp(1), getComp(2), getComp(3)) as T3<A, B, C>
}
operator fun <A, B, C, U : Unapply3<A, B, C>> KClass<U>.invoke(
pa: Pattern<A>,
pb: Pattern<B>,
pc: Pattern<C>
): Pattern<Unapply3<A, B, C>> = {
val (a, b, c) = it.unapply()
it.javaClass.simpleName == this.simpleName && pa(a) && pb(b) && pc(c)
}
The equivalent example code would look now like this:
data class Person(
val firstName: String,
val lastName: String,
val age: Int
): Unapply3<String, String, Int>
The example pattern would be now Person::class(startsWith("Dan"), any, gt(18))
.
Getting rid of KSP would be a very nice improvement, but the need to specify the generics is inconvenient, and it isn’t even type save, at least I found no way to enforce that the type parameters of the respective Unapply
interface match the types of the constructor args.
What do you think of this idea, and which version (KSP annotation vs interface) would you prefer?