Generics question


#1

Hi, I have another question, this time about generics :)

 
public class Test {
   private val <T : Any> field = HashSet<T>()

  fun test() {
  field.clear()
  }
}

`field.clear()` doesn't work because the compiler can't infer what `T : Any` is, and asks me to specify it. Why can't I just call that method? Is there any way of me to specify the type? (`Any` is just an example, my real code is a bit more complex but with the same issue)

Thanks!


#2

It's actually a bug: the compiler should have refused to use `T` inside the initializer `HashSet<T>()` in the first place.


#3

I've reported it: https://youtrack.jetbrains.com/issue/KT-8582


#4

Oh, I see. So is there any way of defining a field and giving it a generic type, without that generic being part of the class definition?


#5

I've noticed that the following also fails to compile, but I guess this one really should be allowed?

 
public class Test {
   fun test() {
      test2()
   }

  fun <L : Any> test2() {
  
  }
}


#6

I don't think it should be allowed, beacuse the compiler has no source of information to infer teh type argument for L


#7

Sorry, but I don't really understand that :)

What is going to change with the compiler knowing that information? L already has a bound, and calling that method with a ddifferent value for L is not going to change anything, is it?


#8

L always has a bound, if it's not explicitly written out, its "Any?".

Kotlin does not use upper bounds as defult values for type parameters, because we feel it is less safe than asking for an argument explicitly.


Allow typealias to have the same name as generic class
#9

So I've found a workaround that works for now, but I guess you're going to remove this from working in the future?

 
private val <L : Any, T : MyThing> field = HashMap<Class<T>, MutableList<OtherThing<L, T>>>()

override fun reset() {
  val temp:HashMap<Class<MyThing>, MutableList<OtherThing<Any, MyThing>>> = field;
  temp.clear()
}

Is not very nice, but it works. I still don't understand why the compiler doesn't do this (replace L with its upper bound) but you might have some reasons why not.

My question is, if this stops being available in the future, can I do something today to make this work (without the workaround) that will be supported in 1.0? Or this won’t be supported at all? I hope it is, because it’s very useful in some cases…


#10

First of all, the code you are trying to write is incorrect by nature:

The property is initialized only once, and being able to refer to it pretending that the same collection has different type arguments every time is prone to errors: I could put an Int into in and later try to take a String out, which would definitely fail.

This is why the compiler should prohibit using type parameters of a val in its initializer.


#11

I don't see where you can put an Int and get a String back, My collection contains Class objects and lists of OtherThing. The only reason I'm using generics in my piece of code is so when you add or retrieve a Class, the type of it should match the collection you get back. I guess L: Any can be removed, as it's doing nothing, but still I don't understand where you can add Ints to get Strings :)


#12

Consider this:

val <T> foo = HashMap<Any, T>()

val foo1: HashMap<Any, Int> = foo
val foo2: HashMap<Any, String> = foo

foo1[“key”] = 1
println(foo2[“key”].length())


This throws an exception. In your particular case it may not be possible (I’m not sure), because of the nature of Class<T>, but the compiler knows nothing about the special nature of it, and should reject this code as dangerous.


#13

Ok, so you were referring a different code than mine, and about the nature of my workaround as well. I understand the current status of this is a bit broken as you masterfully explained with this example, but I still think there should be some way of doing this. If you check my example, I think it's very solid, but I'll try to find another way of doing it, or just use "unsafe" code, casting everywhere...

What I’m trying to do is store a map of Class objects and list of KMemberFunction1 instances, so I can call them on those objects, as something like a plugin system. Generics in this case are helping me force the caller to specify a correct link between the passed Class object and the function (a call looks like “system.register(this, MyPlugin::class, ::callbackMethod)” which is not perfect, but very short and concise :D)

Thank you for your explanations, they are mostly welcome :slight_smile:


#14

Can you elaborate on what your system should do? Maybe there is a different way to implement it in a typesafe way