Exercise about generics from Kotlin Education presentation

There is a presentation about generics on Kotlin for Education At the end there is a exercise. I can’t figure out how to solve this by changing only ????.

open class A
class B : A()
class C : A() { fun consume(other: A): C = this }

fun <T, S : R, R> funny(
   source: Iterator<????>,
   target: MutableCollection<????>,
   base: ????,
   how: ????
) {
   var result: R = base
   for (value in source) {
       result = how(result, value)
       target.add(result)
   }
}

fun main() {
   val wtf = mutableListOf<A>()
   val src = mapOf(3.14 to B(), 2 to B(), "Hello" to B())
   val c = C()
   funny(src.values.iterator(), wtf, c) { r, t -> r.consume(t) }
}

Is it possible to solve this by changing only ????? If yes, how to do it?

Man this is a doozy of a problem.

I would start by noting the actual types passed to the different parameters of funny, then work from there. So source will be an Iterator<B>, target will be a MutableCollection<A>, base will be C.

So then let’s work through our function. var result: R = base… so base is either S or R. So in our use case, S or R becomes C.

for (value in source), source is an Iterator<B>, so each value is B. Not much we can really infer there.

result = how(result, value), now this is pretty interesting. This is clearly an accumulator function of some kind. Since we know result is R, and this how function takes result and returns result, we know the signature is how: (R, ????) -> R.

target.add(result), so this tells us that target must be a MutableCollection<R>, since result is R and you can add result to target.

So… now we have this:

fun <T, S : R, R> funny(
   source: Iterator<????>,
   target: MutableCollection<R>,
   base: ????,
   how: (R, ?) -> R
)

Looking at our example some more… var result: R = base. Now if we know that target is type R, and in our example, R is A, since target is wtf, our mutable list of A, base is c, an instance of our C class, which extends A. So similarly, base must be of type S, since S extends R.

fun <T, S : R, R> funny(
   source: Iterator<????>,
   target: MutableCollection<R>,
   base: S
   how: (R, ?) -> R
)

At this point, I’m assuming that source and how use T, because otherwise it wouldn’t be used… and also, since in our example, R is A, S is C, B cannot be R or S. Ergo… B becomes our T type.

fun <T, S : R, R> funny(
   source: Iterator<T>,
   target: MutableCollection<R>,
   base: S
   how: (R, T) -> R
)

I think that’s the answer. I haven’t tested my code though, so maybe I’m wrong. :slight_smile:

Does that explanation make sense? Were you able to follow my logic? It’s pretty confusing… especially since the classes are all single letters, like the types.

1 Like

Thanks for extensive explanation. Unfortunately it doesn’t work without adding (r as C) to r.consume(t). I’m still wondering if this is possible to solve it without adding (r as C) :thinking:

Huh, that’s a very good point…

I don’t think this function can actually work, then. Since result is of type R, and R doesn’t extend anything, the signature of how has to be (R, ?) -> R, but then the provided lambda wouldn’t work, since if R is A, there’s no consume function on A, and if R is C, then that wouldn’t make sense, because S extends R, but nothing extends C.

Are you sure this is copied as-is from the Kotlin learning site?

Are you sure this is copied as-is from the Kotlin learning site?

Yes, this exercise is at the end of the presentation about * Generics

I don’t think it’s possible… unless someone smarter than me can come along and explain it. how has to be (R, ?) -> R or (R, ?) -> S, not that it makes much difference since the result is just assigned back to R, so even if it was (R, ?) -> S, you’d lose the fact that the return type was S. target has to be MutableCollection<R> since we’re putting result, which is R, into it. funny is being called with wtf as the target, and wtf is mutable list of A, therefore R has to be A… but then how becomes (A, ?) -> A, and the lambda doesn’t work.

I can’t see a way you can make this code work as-is, just by filling in the types. It needs type casting. Unless I’m missing something, which is always possible.

It’s a generics exercise… So, of course, this is the solution:

fun <T, S : R, R> funny(
   source: Iterator<T>,
   target: MutableCollection<in R>,
   base: S,
   how: (R, T) -> R
)

The in R allows R to be narrowed down to C instead of being upcast to A. I really don’t see the point of having S though.

1 Like

Wouldn’t base have to be R? Even if target is MutableCollection<in R>, with the way it’s called, I think R would still become A, which means how is taking an A, not a C, and the lambda doesn’t work.

It compiles!
But to your question, base being R or S doesn’t actually change anything at all. For the sake of simplicity, I’ll talk about a non-S version of the function. The way it’s called basically provides 2 hints for the type of R: a target: MutableCollection<in A> and a base: C. Because we’re using in, the compiler is free to narrow down the type from A to C, and for some compiler spec reason, it prefers the narrower type. I think, very specifically, the information coming from the lambda is only analyzed after this is determined, so only those 2 hints are used. Importantly, without in, R can only be A, because MutableCollection is invariant.

open class A
class B : A()
class C : A() { fun consume(other: A): C = this }

fun <T, S : R, R> funny(
   source: Iterator<T>,
   target: MutableCollection<in R>,
   base: R,
   how: (R, T) -> R
) {
   var result: R = base
   for (value in source) {
       result = how(result, value)
       target.add(result)
   }
}

fun main() {
   val wtf = mutableListOf<A>()
   val src = mapOf(3.14 to B(), 2 to B(), "Hello" to B())
   val c = C()
   funny(src.values.iterator(), wtf, c) { r, t -> r.consume(t) }
}