I am feeling really stupid, but I am still struggl...
# announcements
a
I am feeling really stupid, but I am still struggling with declaration site variance in some places. I have a node-like object of a type
T
and it could produce other nodes that could possibly extend
T
. Then I have a builder object for that tree that could consume
T
or something extending T:
Copy code
class Tree<T>{
  fun getNode(name: String): Tree<T>
}

class Builder<T>{
  fun setNode(name: String, tree: Tree<T>)
}
In first case I can possibly work without variance at all, but in second case I need some kind of variance to be able to put nodes of
something: T
inside. How should I do it? Just putting
in
variance in class header does not work.
k
Since
Tree
is a producer, you should add the
out
variance propagate this information. Builder on the other hand is a consumer, which will then work with
in
.
Copy code
class Tree<out T>{
    fun getNode(name: String): Tree<T> = TODO()
}

class Builder<in T> {
    fun setNode(name: String, tree: Tree<T>): Unit = TODO()
}
If however, you do not apply any variance info on Tree<T>, it will default to invariant, then applying
in
to Builder will be an error, since T occurs as both co and contra variant according to Tree’s definition
a
It does not work this way in both cases. In first case I have an error stating that parameter
T
declared as
out
but used in invariant position. While I am producing a
T
object everything is fine, but if I want
Tree<T>
it does not work. In second case I get the same error and furthermore, I can't build the tree of type
Tree<T>
from the builder since it is out operation.
k
So, if I get you correctly,
Tree
also needs to consume some kind of T right? That means Tree<T> should indeed be invariant
a
No, it does not. Simply writing
fun getNode(name: String): Tree<T>
creates a error for reasons I do not fully understand. The builder obviously needs to somehow produce the result, so it is a litle bit more complicated.
k
This works for me
Copy code
class Tree<out T>{
    fun getNode(name: String): Tree<T> = TODO()
}
Builder on the other hand, should be invariant, since it will eventually produce a
Tree<T>
as well. On the other hand, you can leave both declarations invariant, and revert to use site variance. So, the builder method will look like:
Copy code
fun setNote(name: String, tree: Tree<out T>)
a
I've done just like that. But why it is
out T
and not
in T
? It is consumed after all.
k
The
out
is being set on Tree and not the builder
a
I understand that. But the tree is being consumed. OK, I understand that it corresponds to Java
extends
, but still it breaks the logic somehow.
k
How so. Accepting a
Tree<out T>
simply asserts that you won’t be able to use any
Tree#consume(T)
methods in that
setNode
. If the Builder were invariant, then you can apply in/out to Tree<T> parameter as necessary. However, if the builder itself were to be a consumer, then the only assurance there is, is that t will at least be a type of the upper limit of T, automatically forcing it to be
Tree<out T>
Not sure if this explains it clearly
a
I will try to think about it more.
Whatever I do, I can't manage this:
fun nodeStream(): Stream<Tree<T>>
to work.
Invariant works of course.
Stream itself is a producer, so it should be possible in theory
k
Stream is producing a Tree, but it doesn’t care whether or not T in tree is in or out right? If Tree already has a variance, then it will be automatically applied, otherwise, I think you should be able to specify in/out
a
Currently I managed to do everything using use site variance like in the java code I was porting. I does not seem that declaration site variance helps much in such complicated cases. Thanks for the help anyway.
k
You’re welcome. Variance is still one of those things I haven’t managed to understand very well myself. It quickly gets so tricky 😞
a
Yes it does. Kotlin seems a bit better than java in this regard, but not much.
k
I think the concept of variance itself is the tricky one. The easiest cases are when your class simply uses T directly either as in/out With nested generics, it can easily get out of hands. Kotlin only tries to make it appear less complicated. But still
i
@altavir
Stream
is a java interface, therefore it doesn't contain any declaration-site variance info, so it is invariant.
a
Thanks. This is the first time I hear about that. Probably should read documentation again.