1.1-M04 dropped support of nested typealias

Is there a technical reason why nested type aliases were dropped in the latest iteration?

I find them to be extremely valuable for increasing code readability, especially in generic classes or where I have complex data structures such as map of maps, etc.

6 Likes

Yes. We encountered numerous design issues for which we were unable to find an adequate resolution. @Dmitry_Petrov_1 should be able to provide more details when he’s back from vacation.

1 Like

that is unfortunate, so far it has had a huge negative impact on the readability of the code I’m working on, after I expanded all the nested type aliases I used to have.

please bring it back. kotlin is amazing and even though it’s new syntax is logical and fit it’s features, it quickly gets verbose and type aliases went a long way to alleviating the verbose

It’s most definitely not coming back in 1.1, and I’m not sure if it would be possible to reintroduce it in later versions.

1.1-M04 was the first 1.1 release that I tried, so when I encountered the lack of support for this, I just assumed it was something that hadn’t been implemented yet. Some details from @Dmitry_Petrov_1 would be of interest.

(This reply is posted so that he knows someone is in fact curious.)

1 Like

I’ve found an answer in slack for the similar question from Dec 22nd, and I’ll quote it here:

1 Like

Problem with type aliases declared in classes and interfaces is that they can capture type parameters from the surrounding scopes without capturing the receiver.

I don’t see what that means. I’d love to see a code example of how it causes a problem.

One of the things “made possible” is dependent types. For example I may have the following:

interface KeyElem<K> {
   typealias Key=K

   val key: Key
}

class Container<E:KeyElem<*>> {
   operator fun getValue(key:E.Key): E = elems.first { it.key == key }

   val elems: List<E>
}

This is not valid Kotlin, but for example allows access to the key parameter without specifying it as a type parameter on Container. This usage is possibly “straightforward” but what happens with inheritance? Can an alias be overridden, or be abstract?

Did you mean “dependent” type, or something else? I though dependent types were types parameterized by values [1]. Eg, List<String, 3>

[1] A gentle introduction to dependent types « Otaku – Cedric's blog

class Outer<T> {
  inner class Inner
}

class Host<T> {
  typealias Problem = Outer<T>.Inner
}

Here type alias Problem captures a type parameter T from Host.
Consider type alias constructor for Problem. It captures type parameter T from Host and has a receiver of type Outer<T>. This is impossible in the resolution algorithm used in Kotlin 1.0 and 1.1, and is tightly intervened with the design for specialized generics, which is in a rather early stage (and is required for Kotlin Native and Java 10 interoperability; google “species static” if you are curious).
There are also some problems with type aliases inheritance (that currently makes type aliases in interfaces not quite useful).
If we support them now “with some limitations”, it turns out we can’t remove those limitations later, because it would be a breaking change in the resolution scheme (which is a big no-no).
So, we decided to wait until we have a more or less stable design for new resolution scheme that would be able to support such declarations.
We do consider nested type aliases useful, and they are still in the plans, but not for 1.1.

Let’s separate problems with the logic of the language, from a user’s point of view, from problems with the compiler implementation. If you’re saying there is a problem with the latter that makes implementing nested aliases a major pain, then I just accept that. But if you’re saying there is something wrong at the language level, then I don’t see and I’m still confused.

I can write code without the alias, without a problem:

class Host<T> {
    fun <U> foo(o: Outer<U>) {
        // need an instance of Outer to create an Inner
        val inner = o.Inner() 
    }
}

object RunIt {
    @JvmStatic
    fun main(args: Array<String>) {
        val outer = Outer<String>()
        val host = Host<Integer>()
        host.foo(outer)
    }
}

So if Problem were an alias for Outer.Inner…

val inner = o.Problem()  // in method foo(...) above

[Edit] Do you mean that it looks for the name Problem only the type of o: Outer, so it won’t find it? That makes sense, but at least it theoretically could work, or … might it be very useful to allow nested type aliases but just not for inner classes?

@rnikander
Type alias constructor for Host<T>.Problem should have a dispatch receiver of type Outer<T> and should be invokable in “species static” scope for Host<T> (which captures T, but doesn’t require a receiver of type Host<T>).
This is not the case for Host<T>.foo: it is either a member of Host<T>, or a member extension in Host<T>. Both would require a dispatch receiver of type Host<T>.
In fact, such function can’t be defined in Kotlin 1.1, and that’s exactly the problem here.

While you are doing the design, there’s some options you may want to consider:

  • if aliases can be inherited a SELF implicit alias that is basically the type of the this pointer. Basically this would allow typesafe clone methods and/or comparison fun equals(other:SELF) but can have wider use for example in a base class representing a tree with nodes where the nodes should all be of the same type. This is almost possible with recursive generics: Interface Node<V:Node<V>>, but that is very awkward (not always typesafe) and explodes if other generic type parameters are needed (you easily end up with 80 characters of type parameter)
  • For a type with generic parameters, allow referencing the variable on instances of it (even if they don’t have a concrete type):

(make the following code possible):

interface A<T> {
    val a: T
    val b: T
    fun doSomething(aOrB:T)
}

fun useA1(x:A<*>) {
    val p:x.T = if (myState) x.a else x.b
    x.doSomething(p)
}

fun useA2(x:A<*>) {
    val p = if (myState) x.a else x.b
    x.doSomething(p)
}

It would be very nice to be able to have the following work properly (I’m not stuck on the syntax) as expected:

interface IBuilder<T> {
  fun build: T
}

interface Buildable {
  typealias Builder = IBuilder<out SELF>

  fun builder(): Builder
}

@pdvrieze
Thanks for your input.
Type checking for x.T requires additional features in Kotlin type system that we are rather reluctant to add (full-blown existential types and all that).
Self types were present in early (long before 1.0) versions of Kotlin, but caused some issues with Java interop and were dropped.

Interesting to hear what you looked at. One other observation though is that both the compiler and plugin at the moment really don’t seem to like generics very much at all (I know the code is much more complex than desirable), especially recursive generics in compilation speed and indexing. If you want to have an example, I’ve got a small-ish SQL mapping library (GitHub - pdvrieze/kotlinsql: A kotlin library that allows abstracting away sql syntax and provides a typed/typesafe alternative that is still close to the metal. - it uses some generated code, easiest is to compile using gradle first time) that doesn’t make Kotlin very happy.

@pdvrieze
We’ll take a look at it.

Would it be possible to allow a limited version when nesting?
i.e. Disallow generics on typealias when nested?

Are there any news on this topic? Is there a ticket to watch?

Re-adding support might also help mapping Swift inner classes exported for ObjectiveC back to their original name.