Pihentagy
06/10/2024, 11:27 AMfire method typesafe? I mean, how can I filter out those Rules, where clazz is incompatible with the arg?Joffrey
06/10/2024, 11:35 AMRule<*>.fire() with an incompatible argument. If you declare this function to only take T, it cannot take other types. You might instead want to only call rules that do support your type, using the clazz property to make this check.Pihentagy
06/10/2024, 11:42 AMPihentagy
06/10/2024, 11:43 AMTobias Suchalla
06/10/2024, 1:37 PMclass RuleSet(val rules: MutableList<Rule<*>>) {
inline fun <reified T : Any> fire(arg: T): Set<Rule<T>> {
return rules
.filter { it.clazz == T::class }
.filterIsInstance<Rule<T>>() // logically redundant but compiler-friendly
.filter { it.fire(arg) }
.toSet()
}
}
Because we need T to be reified, the function needs to be inline, thus rules needs to be public.
Tested with class RuleSet and standalone function:
import kotlin.reflect.KClass
fun main() {
val rules = listOf(
Rule(Int::class, "Int1", { this > 0 }, { println("fired $name") }),
Rule(Int::class, "Int2", { this > 0 }, { println("fired $name") }),
Rule(String::class, "String1", { this != "" }, { println("fired $name") }),
Rule(Boolean::class, "Boolean", { this }, { println("fired $name") }),
)
println(rules.fire(1))
val ruleSet = RuleSet(rules.toMutableList())
println(ruleSet.fire(1))
}
class Rule<T : Any>(
val clazz: KClass<T>,
val name: String,
private val whenTrue: T.() -> Boolean,
private val thenDo: Rule<T>.(T) -> Unit
) {
fun fire(arg: T): Boolean {
if (whenTrue(arg)) {
thenDo(arg)
return true
}
return false
}
}
class RuleSet(val rules: MutableList<Rule<*>>) {
inline fun <reified T : Any> fire(arg: T): Set<Rule<T>> {
return rules
.filter { it.clazz == T::class }
.filterIsInstance<Rule<T>>()
.filter { it.fire(arg) }
.toSet()
}
}
inline fun <reified T : Any> List<Rule<*>>.fire(arg: T): Set<Rule<T>> {
return this
.filter { it.clazz == T::class }
.filterIsInstance<Rule<T>>()
.filter { it.fire(arg) }
.toSet()
}Joffrey
06/10/2024, 1:43 PMclass RuleSet(private val rules: MutableList<Rule<*>>) {
fun <T : Any> fire(arg: T): Set<Rule<in T>> = rules
.filter { it.clazz.isInstance(arg) }
.map {
@Suppress("UNCHECKED_CAST") // we checked it in the filter above
it as Rule<in T>
}
.filter { it.fire(arg) }
.toSet()
}
Which does require the clazz property still.
It doesn't need T to be reified, nor fire to become inline. We are using reflection anyway for the instance checkJoffrey
06/10/2024, 2:18 PM== check is a bit restrictive. It means we won't fire rules that can handle a supertype of the valuePihentagy
06/10/2024, 2:26 PMJoffrey
06/10/2024, 2:42 PMit.clazz.isInstance) means you don't need the reification for the actual check. You would need it for filterIsInstance if it were actually testing something with T, but it doesn't check the type parameter here so it only works with Rule as a raw type, hence why you can get away without reifying T (we're kinda exploiting a loophole here).
This is why I prefer using the map approach with explicit cast and warning suppression to show it's just a compiler trick.Joffrey
06/10/2024, 4:14 PMit.clazz.isInstance(arg) then you cannot cast your rules to Rule<T> (again, don't trust filterIsInstance, you're being fooled and the compiler too). It should be Rule<in T>, because the rule may in fact be defined for supertypes of the current T, so their clazz property will not be T.Pihentagy
06/10/2024, 4:33 PMPihentagy
06/10/2024, 4:35 PMPihentagy
06/10/2024, 4:36 PMJoffrey
06/10/2024, 4:36 PM== (as in Tobias's code), you're effectively checking that T is the type of the rule, so it's a Rule<T>. When you're checking with clazz.isInstance(arg), you're checking that the type T is acceptable by the rule, and is a subtype of the rule's parameter. So you're only checking that it's a Rule<in T>.Joffrey
06/10/2024, 4:36 PMIs there a kotlin playground, where I can paste?https://play.kotlinlang.org/
Joffrey
06/10/2024, 4:37 PMval appliedRules: Set<Rule<Int>> = ruleSet.fire(42) // incorrect
Here it's wrong to say that appliedRules only contains rules of type Rule<Int>. It may contain Rule<Comparable<Int>. If you try to access appliedRules.first().clazz it will give you a wrong class type.Pihentagy
06/10/2024, 4:42 PMJoffrey
06/10/2024, 4:43 PM