Why the scattered generic types?


#1

Consider the following function signature:

fun <NI> addSortedSection(name: String, namedIdentifiables: List<NI>, category: Category)
        where NI : Named, NI : Identifiable {

I find this hard to read because my reading process goes like this:

  1. “Ah, there’s a generic parameter NI…”
  2. “…it’s used in this list parameter so I guess it works for all lists…”
  3. “…no, wait, there’s a where-clause that restricts it. Better read the signature again, this time with the newly aquired type restriction knowledge in my head.”

What buggs me is that in Kotlin, function signatures go “types + name + parameters + types” i.e. the types are split, some are at the start, some are at the end of the signature.

I would find it easier to read if I could write it like this:

fun <NI where NI : Named, NI : Identifiable>
addSortedSection(name: String, namedIdentifiables: List<NI>, category: Category) {

I think this is easier because it first tells you everything you need to know about the types so you already know everything about them when you spot them in the parameters.

What is the reasoning behind putting the type restrictions at the other end of the signature?


Multiple constraints on a type parameter - design decision
#2

The main reason is syntax.

fun <NI where NI : Named, NI : Identifiable>

This wouldn’t parse too well, because we can’t know whether NI : Identifiable is another constraint or a declaration of the next type parameter. This, of course, could be mitigated by using a different separator (e.g. ;) for type parameter declarations, so we’d write things like this:

fun <K; V; K1> Map<K, V>.mapKeys(mapper: (K) -> K1): Map<K1, V> 

One can argue that this is not too bad, but we think that similarity to existing languages in this case outweighs the (very rare) issue with where at the end of a signature.


#3

What about the Java syntax?

fun <NI : Named & Identifiable> ...

or more general

fun <NI where NI : Named && NI : Identifiable && whatever condition you like> ...

This is actually using two separators, but different ones and is much less prone to mistakes (I’d write comma instead of semicolon all the time, as comma is the item common separator). Or use and for the second separator to make it more English.

IMHO such a restriction is pretty common in Java and probably also in Kotlin…


#4

The Java syntax doesn’t scale to other kinds of type parameter constraints (e.g. the no-argument constructor constraint that has been requested on multiple occasions). The && syntax could probably work. However, we had a fairly hard time coming up with meaningful examples for multiple type parameter constraints, so it doesn’t look like these restriction actually come up often enough to invest any effort into changing the current syntax.


#5

Just today, I was looking at my

<E extends SimpleEntity<E> & OwnedEntity & Sized<E>,
    I extends SimpleEntity<I> & Item<E>> doSth(...) {...}

just to find out, it can’t be simplified. :smiley: But agreed, it’s not very common. I guess the easiest solution would be to move the where clause where it IMHO logically belongs.

fun <NI where NI : Named, NI : Identifiable> ...

would read like

 ∀ NI, NI ∈ Named, NI ∈ Identifiable: ...

But you’re right, there are too many more important areas.


#6

I agree it’s not used a lot.

In my opinion I believe, the best option (witch also avoids parsing problems) is to repeat type declaration with different type constraints T:A, T:B, K in initial generic type declaration (since it is already repeated after when clause)