Allow typealias to have the same name as generic class

Consider this code:

open class Foo<T: Bar>
open class Bar

Suppose that T is actually needed for the inheritors of Foo, not for the users:

class MyFoo : Foo<MyBar>()
class MyBar: Bar()

Suppose now that Foo is generally useful by itself. But actually we don’t want to concern users with the mechanics of choosing the proper Bar descendant. What we would like to see is them using Foo, meaning Foo<Bar>, just as MyFoo means using MyBar.

One solution for that would be to allow using generic type where parameters are explicitly bounded without these parameters, substituting the bound instead. But seems like language designers are against this:

Another possible solution would be to allow defining typealias with the same name as the generic type:

typealias Foo = Foo<Bar>

Currently, Kotlin doesn’t allow that. On the surface, it looks like it’s always possible to disambiguate: whenever you see a symbol without type parameters, that’s the type alias.

1 Like

So you would also want to allow this?

class Foo<T> {}
class Foo{}

The naming for typealiases follows the same rules as the naming of classes (as far as I know), so this would be a consequence of allowing this. And I personally don’t see the problem with

typealias FooBar = Foo<Bar>

No, I wouldn’t want to allow this for classes. I don’t see why the rules need to be the same: type aliases do not introduce types, whereas classes do, and I don’t want to have two different types with the same name. With type aliases, the type remains the same, so why can’t I name it the same way (but without the type parameters)?

As for the FooBar suggestion, it would kinda work if applied backwards:

typealias Foo = FooBar<Bar>
open class FooBar<T: Bar>

That’s what I would do now, but it would be nice if I didn’t have to, because I still have to invent a synthetic name. For me, synthetic naming signifies an opportunity to improve the language.

I think in this case the synthetic name is good as it differentiates the type from the generic class, which makes the code more readable in the end. Ask yourself this, what do you gain by having a generic class named the same as any type? There would be confusion about what you are referencing. Do you reference the type or the class? It might be possible to tell from the context, but it would make your code harder to read.

Also I guess you would want to only allow

class Foo<T: Bar>
typealias Foo = Foo<Bar>

but what about this

class Foo<T: Bar>
typealias Foo = Baz

So you would want to allow only one additional typealias for class Foo<T: Bar>
typealias Foo = Foo<Bar>
or do you also want to allow

class Foo<T: Bar>
open class Bar
class Baz: Bar()
typealias Foo = Foo<Bar>
typealias Foo = Foo<Baz> // in a different file

If you read any code now, what does Foo mean? Is it Foo<Bar> or Foo<Baz> or is the class referenced?
Obviously the compiler could still tell, but for the programmer this becomes really hard to understand, especially if there are a lot of child types of Bar.

And I don’t see what you would gain by changing this? You would only save a few characters for the readability of the code. I don’t see where this is a good trade. Maybe you could give a real code example where this would actually create a benefit.

I see this suggestion as a limited way to have “default type parameters” for generic types. That is, instead of allowing all generic types to have default parameters, make it opt-in. Maybe there’s a better way to do that, e.g., by having some kind of annotation on the type parameter, or something. However, for now I don’t see how allowing type alias to have the same name as a generic type is bad.

You can tell by looking if there are angle brackets. No angle brackets = type alias. Angle brackets = generic type.
In the motivating example, it really means “the default values”, which is quite natural and intuitive.

I don’t see the point of protecting from a deliberate misuse. If the type alias is in the same package as the class, then the programmer should know what they are doing. If it’s in different package — well, you can have two classes with similar names from different packages, people seem to accept that.

As for your example with the same typealias in a different file — it’s a compile error to do that in the same package. If it’s in a different package, well, you can have two classes with the same name in different package doing completely different things.

Among the designs discussed so far the most promising is to have “default values” for type parameters, i.e.:

class Foo<T : Bar = Baz>

This would likely solve your case, I think, but I can’t give you an ETA for this feature

13 Likes

Thanks Andrey! I had an impression that you were against default values for type parameters. Glad to be wrong here :slight_smile:

I’d definitely love this feature. Could help a lot in our current project. Is there any plan about when this feature will be implemented?

Can’t give you an ETA at the moment, but it’s on our short list of features for future versions (1.4 and on)

7 Likes

Would really like to have this, so I can avoid having to maintain multiple classes to achieve the same task.

I would normally have to do something like this

interface I1<T1 : Any, T2: Any>
{
    val p1: T1
    val p2: T2
}

interface I2<T1: Any> : I1<T1, SomeOtherTypeHere>
interface I3<T2: Any> : I1<SomeOtherTypeHere, T2>

Imagine how much this will grow should the requirements change, having to support three or four generic parameters which are independent of each other. Big problem, please do it, I would really love it.

Will it be allowed to asign template for template?
fun <Base, Impl : Base = Base>doWork() ?

This feature is on the table (they are not templates, and this is not an assignment, but I think I get what you mean)

1 Like

any news about this feature req?