Generics validated at instantiation – same syntax, far more powerful


#1

I find generics to be rather rudimentary, mostly useful in container rather than algorithmic contexts. I’m thinking of (oversimplified) examples like:

fun <T>add(x: T, y: T) = x + y
fun <T>factory() = T()

This is about making an existing Kotlin feature far more expressive, simply by deferring part of the check to the generic’s points of instantiation. That is exactly what would happen if I copied it and replaced T manually.

Because sadly (but alas logically) we don’t have

fun Number.plus(other: Number): Number

it’s not possible to use + even if the parameters are <T: Number>. And also T is not yet known to have a parameter-less constructor.

Therefore I suggest only requiring generic definitions to be lexically correct. They need to make full sence, only at the point where they are instantiated – kind of safe macros. Kotlin could just check generics for syntax errors, and keep their source code or AST with original file name and line number in memory. Then it would “read” it again, locally type aliasing the generic parameters. That should be neither hard to do, nor expensive (except memory if you have millions of generics)…

(Edit: summarized some of the discussion into 1st post)


#2

I think that’s whats typealias are for, generally I think the current way is a better way of preventing runtime errors


#3

I am really puzzled by this answer. I was thinking of (oversimplified) examples like:

fun <T>add(x: T, y: T) = x + y
fun <T>factory() = T()

The compiler parses these fine, but complains about unmet conditions. Yet, instantiated with the right kind of T, these would be fine, still at compile time.

Do you see typealias even being related to this?


#4

I once thinking of typealias as being a safe kind of macro, they make thing look much more sense and the the compiler generally doesnt complain about it.


#5

I’m afraid I don’t get what you’re trying to say. A typealias can be seen as the opposite of generic, in that <T> makes T be an alias to potentially many different types inside of that definition.

But can you please be concrete, how you want to use them to write algorithms applicable to any type (having the needed constructors, operators and methods)?


#6

I think the only solution right now for that kind of function is to use a when statement and go through all the types you are expecting with smart casting. Or limit your scope of generics to a specific type with where


#7

Please note the tag of this discussion! This is about making an existing Kotlin feature far more expressive, simply by deferring part of the check to the generic’s points of instantiation. That is exactly what would happen if I copied it and replaced T manually.

I’m not asking for workarounds, obviously anything can be achieved somehow. Though probably not the way you suggest:

  • A big when would be cumbersome, where Kotlin is being touted as being concise.
  • Each branch of the when would return a different type, casting them all to Any – not very useful.

#8

Still I don’t think it would be a good idea, if you have a broad add function, how do you determine how those two generic objects should be added to each other. Anyone could do add(someList, someMap) which doesn’t really make sense to me


#9

Concretely I stumbled over this for a Complex type where the user could choose the underlying type for real and imaginary depending on their precision needs.

It’s near impossible to overload the customary Complex arithmetic rules if the members are <T: Number> – even though all the needed combinations exist for each concrete T. It’s crazy to have to bend over backwards for something so straightforward!


#10

What you seem to want is basically what C++ templates provide. If you want that you need concepts as well (you are creating compile-time code and need type safety there as well otherwise you get horrific error messages).


#11

Kotlin could just check generics for syntax errors, and keep their source code or AST with original file and line number in memory. Then it would “read” it again, locally type aliasing the generic parameters. That should be neither hard to do, nor expensive (unless you have millions of generics)…

But right, it’s true that C++ can do some amazingly crazy things here.

As for the indigestable error messages, that’s just because g++ doesn’t keep track of what type the user actually provided, instead showing behind-the-scenes traits and allocator internals.


#12

Kotlin is a statically typed language which supports Java interoperability. Your suggestion would basically remove both of these items (a library function such as factory() would not be statically checkable, and would not be callable from Java).


#13

I never liked Java, so I don’t know it well – I’ll just take that as an unfortunate show stopper. Maybe some @notJavaCallable annotation might help not to cripple Kotlin in such cases…

As for the statical typing, I don’t see my proposal breaking it. The generic factory is not a function, but the concrete factory<MyClass> is, with all types known at compile time. That Kotlin often automatically derives the generic type, is convenient but doesn’t change it being statically known.


#14

A Java (or Kotlin) project typically consists of multiple separately compiled modules. In a lot of cases, the definition of the generic class will be part of a library, and its usages will be in applications using that library. Not being able to type-check when compiling the library is a strong enough restriction for me to say that your suggestion breaks Kotlin’s static typing. (It might be OK for something like factory, but by the time you get to ArrayList, you might as well switch to Python.)


#15

Not to start a new thread with the similar topic. There is one thing that takes a lot of time to work around in Java but probably is solved or will be solved in Kotin.
It is very common situation, when one needs an object to produce new Object of the same type. For example copy operation of chain builders. It is simple when one have only one type, but is extremely complicated when there is a hierarchy or even generics. It could be solved by generics, but looks ugly:

class SomeClass<T extends SomeClass>{
  T copy();
}

class SomeClassExtended extends SomeClass<SomeClassExtended>{
  SomeClassExtended copy();
}

Obviously, accessing specific object type to infer the returning type of the method does not violate static typing, but is not possible in Java. Does kotlin solve this problem?

By the way, it is not just aesthetic problem. The solution above has a lot of side effects. For example, there are some problems when the class type is used a parameter in some other generics:

class SomeClass<T extends SomeClass>{
  List<? extends T> getChildren();
}

#16

Unfortunately recursive generics are not solved yet in Kotlin. The slightly less generic problem of self types (which would be even better here) is not solved either. Overall the generics system is still very much like Java to allow for good interop, but it is a limitation.