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 Thomchom
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 Anyephemient
04/26/2023, 3:58 AMout T is: if T is allowed, so is any supertype of Thomchom
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 Anyephemient
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 AMTephemient
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