Using multiple interfaces on a class, how to get v...
# getting-started
j
Using multiple interfaces on a class, how to get values from all interfaces without knowing which they are (ex. generics)
Copy code
enum class Result {

}
sealed interface Review

sealed interface LengthReview : Review {
    val length: Result
}

sealed interface WidthReview : Review {
    val width: Result
}

data class Foo(
    override val length: Result,
    override val width: Result,
) : LengthReview, WidthReview

fun Review.getAllValues(): List<Result> = buildList {
    // Get all sealed subinterfaces of Review
    val list = mutableListOf<Result>()
    // get result from length and width
    if(this is WidthReview) {
        list.add(width)
    }
    if(this is LengthReview) {
        list.add(length)
    }
}
Without defining every sub-interface in an if(this is ..Review), when() would be nice, since it forces me to check every subinterface
Copy code
when(this) {
is WidthReview
...
}
But when stops on match in kotlin
Copy code
val results = this::class.declaredMemberProperties.mapNotNull {
    val value = it.getter.call(this)
    if (value is Result) {
        value
    } else {
        null
    }
}
This works, but not really what I wanted
d
There IMO are two situations: • Either all your Review subinterfaces have the
Result
property in which case it would be sensible to have
val result: Result
already in
Review
• Or not all your Review subinterfaces have the result in which case your current code seems perfectly appropriate. Of course, you can try to use reflection but in my experience, it's generally not a good idea to use it in these specific cases (in contrast to generic libraries like JSON handling).
Expanding on the second situation, if you have many (but not all) subinterfaces with
Result
you might consider creating another common/base interface for them.
j
The thing is I would like to have it like
Copy code
data class Foo(
    override val length: Result,
    override val width: Result,
) : LengthReview, WidthReview
And in some way get all "reviews"
d
I see. I guess you rather want all Results because if you just get all Reviews then you would still have to find out the property under which the Result is stored.
j
Yeah
d
... and that I think answers your question 🙂 You either use reflection or live with the `if`s. Or you might also revisit your problem on a higher level. Perhaps you don't need this non-standard design after all. Speaking from my own experience 😉
j
Thanks for the input 👍
l
My project is multiplatform, so I can't use reflection when I need it. I've come across similar needs, and (while it is different) perhaps gives you some ideas to work with. The next paragraph is perhaps not important, it just describes the context, and how it's similar to your use case: In my case, I have a high-level object called
APLFunction
that describes a specific instantiation of a function (a call to a function at a specific line of code) in my programming language. Some functions can contain references to one or more other function instantiations (for example, when doing function composition), but most functions do not. The compiler needed to find all functions that are referenced by a function in order to compute closures correctly. If I was pure JVM I could have used reflection to find and references like
val ref: APLFunction
in the class. This was of course not an alternative, so instead I did this (simplified):
Copy code
abstract class APLFunction(val fns: List<APLFunction> = emptyList()) {
   ...
}
An
APLFunction
subclass that does embed other function calls, would then do this:
Copy code
class SomeFunction(pos: FunctionInstantiation, fn0: APLFunction, fn1: APLFunction) : APLFunction(listOf(fn0, fn1)) {
    ...
}
In my application I have hundreds of these, so I needed a concise way to do it, and lots of if-statements wasn't something I wanted to use.
d
That's an interesting solution, but with a big disadvantage: you tightly couple the domain model with a very particular way how it is used 😞
l
Yes, it's part of the protocol that an implementation of the class exposes its subfunctions in this manner. Now, internally, a subclass would add a proprty like:
val foo get() = fns[0]
and then explusively use
foo
to refer to it. In principle, I could add a test case that checks all subclasses of
APLFunction
to ensure they don't have any fields that store values of type
APLFunction
.
j
Thanks for sharing! It's an interesting solution but not as generic/compile safe as I would like to 😬
d
Well, I would argue that it's at least safer than the solution with ifs because there you need to check all relevant interfaces at the call site (possibly many times) but here you would just do it once for all at the declaration site:
Copy code
abstract class ResultsHolder(val results: List<Result>)

data class Foo(
    override val length: Result,
    override val width: Result,
) : LengthReview, WidthReview, ResultsHolder(listOf(length, width))

fun ResultsHolder.getAllValues() = results // you probably don't even need this
But as I said this comes at a price of the tight coupling.
👍 1