https://kotlinlang.org logo
Title
a

altavir

03/25/2018, 6:37 AM
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:
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

kingsley

03/25/2018, 9:11 AM
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
.
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

altavir

03/25/2018, 9:17 AM
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

kingsley

03/25/2018, 9:32 AM
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

altavir

03/25/2018, 9:34 AM
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

kingsley

03/25/2018, 9:38 AM
This works for me
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:
fun setNote(name: String, tree: Tree<out T>)
a

altavir

03/25/2018, 9:40 AM
I've done just like that. But why it is
out T
and not
in T
? It is consumed after all.
k

kingsley

03/25/2018, 9:42 AM
The
out
is being set on Tree and not the builder
a

altavir

03/25/2018, 9:44 AM
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

kingsley

03/25/2018, 10:00 AM
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

altavir

03/25/2018, 10:01 AM
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

kingsley

03/25/2018, 11:01 AM
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

altavir

03/25/2018, 11:02 AM
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

kingsley

03/25/2018, 11:05 AM
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

altavir

03/25/2018, 11:05 AM
Yes it does. Kotlin seems a bit better than java in this regard, but not much.
k

kingsley

03/25/2018, 11:11 AM
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

ilya.gorbunov

03/25/2018, 10:30 PM
@altavir
Stream
is a java interface, therefore it doesn't contain any declaration-site variance info, so it is invariant.
a

altavir

03/26/2018, 8:26 PM
Thanks. This is the first time I hear about that. Probably should read documentation again.