h
Untitled.kt
e
out ModelBase
means "any subtype of ModelBase". which means you don't know if
isValid()
accepts a `model: ModelBase`: it may only accept some (unknown) subtype. the only type which is guaranteed to be a subtype of everything is
Nothing
, and so that is the only type that is legal to pass to
Validator<out ModelBase>::isValid
.
what you likely want is:
Copy code
interface Validator<in T : ModelBase>
fun <T : ModelBase> getValidator(model: T): Validator<T>
variance (not covariance) at the declaration (not use) site
intuitively, because
T
is an input, it should be
in
h
But when I do that I get a different error.
e
yes, because your
getValidator
method cannot be typechecked
the way you've written that interface, it will require an unchecked cast
h
Would you have any suggestion how to solve it?
This Snippet shows the error when I use
in
Which gives the Type mismatch you mentioned
Copy code
Type mismatch.
Required:
Validator<ModelBase>
Found:
ModelAValidator
e
yes, that requires an unchecked cast because you can't know, at runtime, that T = ModelA
h
Do you think Reflection would be the best way to go?
e
no, that doesn't make anything better
if you just drop that free function and instead use
Copy code
sealed class ModelBase {
    abstract fun getValidator(): Validator<ModelBase>
}
data class ModelA(...) : ModelBase() {
    override fun getValidator(): Validator<ModelA> = ModelAValidator
}
data class ModelB(...) : ModelBase() {
    override fun getValidator(): Validator<ModelB> = ModelBValidator
}
then everything will typecheck, although this limits your uses somewhat
h
What do you mean by that?
I wasn’t very keen on adding the getValidator into the Model. I’m not sure if that makes sense, 😕
e
there is no self-type in Kotlin, so it cannot be enforced that
<T : ModelBase> T.getValidator()
returns a
Validator<T>
- it could return a
Validator<some other subtype of ModelBase>
of course, once you declare the override with a narrower type on
ModelA
and
ModelB
, those ones will work... if you know their types statically
alternately, if you require that particular function signature, then you need to perform an unchecked cast.
Copy code
fun <T : ModelBase> getValidator(model: T): Validator<T> {
    when (model) {
        is ModelA -> ModelAValidator as Validator<T>
        is ModelB -> ModelBValidator as Validator<T>
it is unsafe for good reason, though.
a user can write
Copy code
val modelA: ModelBase = ModelA(...)
val validator: Validator<ModelBase> = getValidator(modelA)
validator.isValid(ModelB(...))
which will typecheck, but fail at runtime
h
From your opinion, would you say that I’m struggling to get it done properly because the design of the classes are a bit shitty?
e
pretty much
h
Do you have any other suggestion? Do you think the best solution would be make the classes have the getValidator?
e
it depends on how you need to use them. if that is sufficient, then yes
if it isn't part of the interface that would be great too:
Copy code
data class ModelA(...) {
    companion object {
        val validator: Validator<ModelA> = ...
    }
}
you can't generically call
ModelBase.getValidator()
that way, but there isn't a great way to do that anyway
h
I was just trying to avoid messing Models + Validator. I can’t see them tied together.
e
well you could also leave them completely separate
h
Untitled.kt
I tried what you suggested with the abstract, but it doesn’t seem to compile as well.
e
oh right, because the
Validator<T>
occurs in
out
position
I guess that doesn't work either without unsafe casts
blob thinking fast 1
if there weren't any variance at all,
Copy code
interface Validator {
    fun isValid(model: ModelBase)
}
then it would be possible,
ModelAValidator::isValid
would just have to test that
model is ModelA
first, etc. but that loses type safety in a different way, so may not be an ideal solution either
h
Yep! The whole purpose of this implementation is to avoid having
Copy code
when(model) {
   is ModelA -> //DoBlah
....
}
The real interface I’m implementing has around 5 methods. It would not be ideal to have these checks.
a
Do you have any problem with unchecked cast? You are more knowledgeable than the compiler so use it when you need to, and add
@Suppress("UNCHECKED_CAST")
to suppress the warning. It is also used in Kotlin stdlib, such as here.
e
some unchecked casts are safe, this one is not (as I gave an example of earlier)
it still might be the best solution, but that should be kept in mind
h
We ended up going with this snippet. Do you have any consideration?
e
can be made to crash (see previous) but if it doesn't crash it works