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