homchom
04/26/2023, 3:07 AMstate
might be set to a value of type A in one context and then read as a value of type B in another (where A and B are sibling types but not parent/child).
class Example<out T>(var state: T) // syntax error
However, method parameters also count as “occuring in the ‘in’ position” (as IntelliJ words it) and I am confused about the specifics, especially as the first code snippet of the following two is not allowed, but the second is:
class ConsumerOne<out T> {
fun consume(value: T) {} // syntax error
}
class ConsumerTwo<out T>
fun <T> ConsumerTwo<T>.consume(value: T) {} // allowed
I understand that the latter uses an extension function, which is static and not a member, but that doesn’t explain the difference in covariance to me yet. Specifically, what makes the former unsafe but the latter safe?ephemient
04/26/2023, 3:15 AMfun <T> consume(this: ConsumerTwo<T>, value: T)
which is fine as a declaration (aside from the name this
)ephemient
04/26/2023, 3:17 AMConsumerTwo<out T>
, you can't actually do anything with it that consumes in T
homchom
04/26/2023, 3:30 AMin T
but not in an extension, assuming the class has no state? (I think I can make this assumption because such state would also cause a syntax error, and I don’t see yet how a method parameter inherently consumes T
.)Pablichjenkov
04/26/2023, 3:34 AMclass ConsumerOne<in T>
homchom
04/26/2023, 3:40 AMephemient
04/26/2023, 3:40 AMFoo<out>
means that given types A
and B
such that A
is a subtype of B
, Foo<A>
is a subtype of Foo<B>
for parameters, it's always the other way around. given a fun foo(value: A)
always accepts foo(value = _some instance of_ B)
ephemient
04/26/2023, 3:43 AMfun <out T> foo(value: T)
(or if you were to do the same inside a class Example<out T>
), it could be called with any type, and within the function body, no type would be known. this is completely unhelpful so it is forbiddenephemient
04/26/2023, 3:46 AMclass Example<in T> { fun foo(): T }
, the function must return a value that is an inhabitant of every type. this does not exist, so that is likewise forbiddenephemient
04/26/2023, 3:47 AMephemient
04/26/2023, 3:50 AMPablichjenkov
04/26/2023, 3:54 AMhomchom
04/26/2023, 3:56 AMexample(string: String)
definitely should not accept any arbitrary Any
object.ephemient
04/26/2023, 3:56 AMString
(which there aren't any, because String
is final, but replace it with another type and it'll be more meaningful)ephemient
04/26/2023, 3:57 AMString
such as Any
ephemient
04/26/2023, 3:58 AMout T
is: if T
is allowed, so is any supertype of T
homchom
04/26/2023, 3:58 AMephemient
04/26/2023, 3:58 AMval list: List<Any> = listOf<String>()
is completely fine: any String
you can get()
out
of a List<String>
is also an Any
ephemient
04/26/2023, 3:59 AMout
and in
cannot operate together: one allows any supertype, one allows any subtype, in subtyping relationshomchom
04/26/2023, 4:00 AMephemient
04/26/2023, 4:00 AMhomchom
04/26/2023, 4:02 AMvalue
with println
. So
class ConsumerOne<out T> {
fun consume(value: T) { // syntax error
println(value)
}
}
As far as I know, this doesn’t cause any issues since it just does a simple print, and there’s no properties in ConsumerOne
so I can’t have issues there; what kind of things could be done in the body instead (things that would not be allowed in arbitrary code, like an extension)?ephemient
04/26/2023, 4:03 AMopen class ConsumerOne<out T> {
open fun consume(value: T) {} // if this were not rejected, then
}
class IntConsumer : ConsumerOne<Int> {
open fun consume(value: Int) { println(value + 1) }
}
val consumer: ConsumerOne<Any> = IntConsumer() // permitted, because ConsumerOne<Int> is a subtype of ConsumerOne<Any>, due to <out>
consumer.consume("") // permitted, because String is a subtype of Any
then consume(value: Int)
would be called with an String
, which is brokenhomchom
04/26/2023, 4:04 AMopen
?ephemient
04/26/2023, 4:04 AMhomchom
04/26/2023, 4:05 AMephemient
04/26/2023, 4:09 AMclass Consumer<out T>(val consume: (T) -> Unit) // will not compile, but if it did
val consumer: Consumer<Any> = Consumer<Int> { value: Int -> println(value + 1) }
consumer.consume("")
homchom
04/26/2023, 4:12 AMconsume
in the original example, what could be done?ephemient
04/26/2023, 4:15 AMT
ephemient
04/26/2023, 4:17 AMprintln
doesn't expose it because it is equivalent to
class Consumer {
fun consume(value: Any?) { println(value) }
}
under erasure, and none of the generic typing gives you any additional behavior over thathomchom
04/26/2023, 4:20 AM