Type aliases #4

Closed
abreslav opened this Issue Apr 20, 2016 · 46 comments

Comments

@abreslav
Contributor

abreslav commented Apr 20, 2016

Discussions about the Type aliases proposal will be held here.

@dnpetrov is the shepherd for this proposal.

@abreslav abreslav assigned dnpetrov and unassigned dnpetrov Apr 20, 2016

@cbeust

This comment has been minimized.

Show comment
Hide comment
@cbeust

cbeust Apr 20, 2016

Contributor

Looks great overall. Quick question (sorry if I missed it in my reading): can I pass the effective type where a type alias is expected?

e.g.:

typealias Str = String

fun f(s: Str) { ... }

f("Yo") // Is this valid or do I need to wrap this string in a `Str` first?
Contributor

cbeust commented Apr 20, 2016

Looks great overall. Quick question (sorry if I missed it in my reading): can I pass the effective type where a type alias is expected?

e.g.:

typealias Str = String

fun f(s: Str) { ... }

f("Yo") // Is this valid or do I need to wrap this string in a `Str` first?
@cbeust

This comment has been minimized.

Show comment
Hide comment
@cbeust

cbeust Apr 20, 2016

Contributor

Another point I'm sure you are aware of but that I want to make sure doesn't get overlooked: whenever a compiler error is emitted, please show the type alias and not the effective type.

C++ inflicted years of torture on its developers for failing to do this properly in the early template years.

Contributor

cbeust commented Apr 20, 2016

Another point I'm sure you are aware of but that I want to make sure doesn't get overlooked: whenever a compiler error is emitted, please show the type alias and not the effective type.

C++ inflicted years of torture on its developers for failing to do this properly in the early template years.

@Spasi

This comment has been minimized.

Show comment
Hide comment
@Spasi

Spasi Apr 20, 2016

Could you please explain why "newtype" is out of scope for the first iteration of type aliases? The part under Restrictions mentions that Java (obviously) can't handle type aliases, but it's not clear how that affects the implementation in Kotlin.

I've been looking forward to this feature, see Zero-overhead, type-safe native interop with Kotlin. Afaict, there won't be any type-safety or auto-complete benefits with the current proposal (without "newtype").

Spasi commented Apr 20, 2016

Could you please explain why "newtype" is out of scope for the first iteration of type aliases? The part under Restrictions mentions that Java (obviously) can't handle type aliases, but it's not clear how that affects the implementation in Kotlin.

I've been looking forward to this feature, see Zero-overhead, type-safe native interop with Kotlin. Afaict, there won't be any type-safety or auto-complete benefits with the current proposal (without "newtype").

@soywiz

This comment has been minimized.

Show comment
Hide comment
@soywiz

soywiz Apr 20, 2016

@Spasi I would like that feature too! In haxe it is called abstract: http://haxe.org/manual/types-abstract.html (allowing implicit casts and other stuff). But maybe a bit complicated for a first approach.

Just works for single values (so no pairs or small structures like Point, Size...)

But it is cools for example to wrap Int or Long and totally avoid allocations.

Java has descriptors (and signatures for generic types) so you won't be able to distingish void a(int a) { } and a typealias with the first argument.
But at least on kotlin it would be possible to restrict at compile time its usage.

Our use case:

We have another use-case that would benefit us a lot. In order to use time lapses safely (and work with java6). We have TimeSpan and DateTime classes (with a very similar C# API) and we add extension properties so we can create time literals easily.

data class TimeSpan internal constructor(private val ms: Int) {
    override fun toString(): String = "${seconds}s"
    val milliseconds: Int get() = ms
    val seconds: Double get() = ms / 1000.0

    operator fun plus(that: TimeSpan) = TimeSpan(this.ms + that.ms)
    operator fun minus(that: TimeSpan) = TimeSpan(this.ms - that.ms)
    operator fun times(count: Int) = TimeSpan((this.ms * count.toDouble()).toInt())
    operator fun div(count: Int) = TimeSpan((this.ms / count.toDouble()).toInt())
    operator fun times(count: Double) = TimeSpan((this.ms * count.toDouble()).toInt())
    operator fun div(count: Double) = TimeSpan((this.ms / count.toDouble()).toInt())
    operator fun compareTo(that: TimeSpan): Int = this.ms.compareTo(that.ms)

    companion object {
        fun fromSeconds(value: Double) = TimeSpan((value.toDouble() * 1000).toInt())
        fun fromMilliseconds(value: Double) = TimeSpan(value.toInt())
        fun fromMicroseconds(value: Double) = TimeSpan((value.toLong() / 1000L).toInt())

        fun fromSeconds(value: Int) = TimeSpan((value.toDouble() * 1000).toInt())
        fun fromMilliseconds(value: Int) = TimeSpan(value.toInt())
        fun fromMicroseconds(value: Int) = TimeSpan((value.toLong() / 1000L).toInt())

        fun fromSeconds(value: Long) = TimeSpan((value.toDouble() * 1000).toInt())
        fun fromMilliseconds(value: Long) = TimeSpan(value.toInt())
        fun fromMicroseconds(value: Long) = TimeSpan((value.toLong() / 1000L).toInt())

        fun millisecondsToSeconds(value:Int):Double = value.toDouble() / 1000.0
        fun secondsToMilliseconds(value:Double):Int = (value * 1000.0).toInt()
    }

    fun roundToSeconds() = fromSeconds(seconds.toInt())
}

data class DateTime internal constructor(val timestamp: Long) {
    val month: Month get() = Month.January
    val date: Date get() = Date(timestamp)

    operator fun minus(that: DateTime) = TimeSpan((this.timestamp - that.timestamp).toInt())
    operator fun plus(that: TimeSpan) = DateTime(this.timestamp + that.milliseconds)
    operator fun compareTo(that: DateTime) = this.timestamp.compareTo(that.timestamp)

    companion object {
        val zero: DateTime = DateTime(0L)
        fun nowMillis(): Long = System.currentTimeMillis()
        fun now(): DateTime = DateTime(nowMillis())
    }
}

val Double.seconds: TimeSpan get() = TimeSpan.fromSeconds(this)
val Double.milliseconds: TimeSpan get() = TimeSpan.fromMilliseconds(this)

val Int.seconds: TimeSpan get() = TimeSpan.fromSeconds(this)
val Int.milliseconds: TimeSpan get() = TimeSpan.fromMilliseconds(this)

val Long.seconds: TimeSpan get() = TimeSpan.fromSeconds(this)
val Long.milliseconds: TimeSpan get() = TimeSpan.fromMilliseconds(this)

Example:

fun wait(time: TimeSpan) = ...
wait((5.seconds + 300.milliseconds) * 2)

Since it is a data class which holds a primitive value, having a typealias creating a compile-time type, that in runtime it is just an Int, would totally avoid allocating new objects. And if that kind of typealiases creating new compile-time-only types wouldn't perform implicit casting, would be safe to use them.
Also would prevent namespace congestion for extension methods on primitives.

So i want to be able to do wait(1.seconds) but I don't want to allow wait(1), so that's why right now we require wrapping it instead of just using primitives.

Also this could be used for distances: meters, millimeters...
And for example to hold a RGBA value in a single Int (8 bits per component).

Also you could create a Long that holds two integers and it is treated as a IPoint(x, y) though for floating point numbers it would be more complicated, it would require a Long and to call intBitsToFloat and unpacking + repacking, so it would have overhead and even more overhead on a javascript implementation because it doesn't have long types though javascript GCs are nowadays awesome.

But totally worth for allocation-free integers and doubles wrappers.

soywiz commented Apr 20, 2016

@Spasi I would like that feature too! In haxe it is called abstract: http://haxe.org/manual/types-abstract.html (allowing implicit casts and other stuff). But maybe a bit complicated for a first approach.

Just works for single values (so no pairs or small structures like Point, Size...)

But it is cools for example to wrap Int or Long and totally avoid allocations.

Java has descriptors (and signatures for generic types) so you won't be able to distingish void a(int a) { } and a typealias with the first argument.
But at least on kotlin it would be possible to restrict at compile time its usage.

Our use case:

We have another use-case that would benefit us a lot. In order to use time lapses safely (and work with java6). We have TimeSpan and DateTime classes (with a very similar C# API) and we add extension properties so we can create time literals easily.

data class TimeSpan internal constructor(private val ms: Int) {
    override fun toString(): String = "${seconds}s"
    val milliseconds: Int get() = ms
    val seconds: Double get() = ms / 1000.0

    operator fun plus(that: TimeSpan) = TimeSpan(this.ms + that.ms)
    operator fun minus(that: TimeSpan) = TimeSpan(this.ms - that.ms)
    operator fun times(count: Int) = TimeSpan((this.ms * count.toDouble()).toInt())
    operator fun div(count: Int) = TimeSpan((this.ms / count.toDouble()).toInt())
    operator fun times(count: Double) = TimeSpan((this.ms * count.toDouble()).toInt())
    operator fun div(count: Double) = TimeSpan((this.ms / count.toDouble()).toInt())
    operator fun compareTo(that: TimeSpan): Int = this.ms.compareTo(that.ms)

    companion object {
        fun fromSeconds(value: Double) = TimeSpan((value.toDouble() * 1000).toInt())
        fun fromMilliseconds(value: Double) = TimeSpan(value.toInt())
        fun fromMicroseconds(value: Double) = TimeSpan((value.toLong() / 1000L).toInt())

        fun fromSeconds(value: Int) = TimeSpan((value.toDouble() * 1000).toInt())
        fun fromMilliseconds(value: Int) = TimeSpan(value.toInt())
        fun fromMicroseconds(value: Int) = TimeSpan((value.toLong() / 1000L).toInt())

        fun fromSeconds(value: Long) = TimeSpan((value.toDouble() * 1000).toInt())
        fun fromMilliseconds(value: Long) = TimeSpan(value.toInt())
        fun fromMicroseconds(value: Long) = TimeSpan((value.toLong() / 1000L).toInt())

        fun millisecondsToSeconds(value:Int):Double = value.toDouble() / 1000.0
        fun secondsToMilliseconds(value:Double):Int = (value * 1000.0).toInt()
    }

    fun roundToSeconds() = fromSeconds(seconds.toInt())
}

data class DateTime internal constructor(val timestamp: Long) {
    val month: Month get() = Month.January
    val date: Date get() = Date(timestamp)

    operator fun minus(that: DateTime) = TimeSpan((this.timestamp - that.timestamp).toInt())
    operator fun plus(that: TimeSpan) = DateTime(this.timestamp + that.milliseconds)
    operator fun compareTo(that: DateTime) = this.timestamp.compareTo(that.timestamp)

    companion object {
        val zero: DateTime = DateTime(0L)
        fun nowMillis(): Long = System.currentTimeMillis()
        fun now(): DateTime = DateTime(nowMillis())
    }
}

val Double.seconds: TimeSpan get() = TimeSpan.fromSeconds(this)
val Double.milliseconds: TimeSpan get() = TimeSpan.fromMilliseconds(this)

val Int.seconds: TimeSpan get() = TimeSpan.fromSeconds(this)
val Int.milliseconds: TimeSpan get() = TimeSpan.fromMilliseconds(this)

val Long.seconds: TimeSpan get() = TimeSpan.fromSeconds(this)
val Long.milliseconds: TimeSpan get() = TimeSpan.fromMilliseconds(this)

Example:

fun wait(time: TimeSpan) = ...
wait((5.seconds + 300.milliseconds) * 2)

Since it is a data class which holds a primitive value, having a typealias creating a compile-time type, that in runtime it is just an Int, would totally avoid allocating new objects. And if that kind of typealiases creating new compile-time-only types wouldn't perform implicit casting, would be safe to use them.
Also would prevent namespace congestion for extension methods on primitives.

So i want to be able to do wait(1.seconds) but I don't want to allow wait(1), so that's why right now we require wrapping it instead of just using primitives.

Also this could be used for distances: meters, millimeters...
And for example to hold a RGBA value in a single Int (8 bits per component).

Also you could create a Long that holds two integers and it is treated as a IPoint(x, y) though for floating point numbers it would be more complicated, it would require a Long and to call intBitsToFloat and unpacking + repacking, so it would have overhead and even more overhead on a javascript implementation because it doesn't have long types though javascript GCs are nowadays awesome.

But totally worth for allocation-free integers and doubles wrappers.

@soywiz

This comment has been minimized.

Show comment
Hide comment
@soywiz

soywiz Apr 20, 2016

Another use case I thought (for compile-time new types).

Simulating unsigned types:

type Ubyte = Int

operator fun Ubyte.plus(that: Ubyte): Ubyte = (this + that) and 0xFF

Right now I'm emulating them with getters/setters in properties and using Int instead (I wanted to use property delegates but I think it would affect performance):
https://github.com/soywiz/ktemu/blob/master/src/com/soywiz/ktemu/cpu/z80/z80.kt#L37

soywiz commented Apr 20, 2016

Another use case I thought (for compile-time new types).

Simulating unsigned types:

type Ubyte = Int

operator fun Ubyte.plus(that: Ubyte): Ubyte = (this + that) and 0xFF

Right now I'm emulating them with getters/setters in properties and using Int instead (I wanted to use property delegates but I think it would affect performance):
https://github.com/soywiz/ktemu/blob/master/src/com/soywiz/ktemu/cpu/z80/z80.kt#L37

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Apr 21, 2016

Contributor

@cbeust

  1. Yes, abbreviated types are equivalent to the corresponding unabbreviated expansions.
    It is mentioned in the proposal, but, probably, requires an explicit example.
  2. Yes, preserving type aliases in diagnostics (and not only diagnostics) is mentioned under "Type aliases and tooling" section.
Contributor

dnpetrov commented Apr 21, 2016

@cbeust

  1. Yes, abbreviated types are equivalent to the corresponding unabbreviated expansions.
    It is mentioned in the proposal, but, probably, requires an explicit example.
  2. Yes, preserving type aliases in diagnostics (and not only diagnostics) is mentioned under "Type aliases and tooling" section.
@cbeust

This comment has been minimized.

Show comment
Hide comment
@cbeust

cbeust Apr 21, 2016

Contributor

@dnpetrov

Thanks for the clarifications.

  1. It's a bit unfortunate since it weakens type safety (I can pass an Int to a function expecting a Dimension) but I understand that having to wrap all these cases would be detrimental both at the source level (forced to wrap everything) and possibly at the bytecode level (necessary boxing, although that could probably be avoided). In other words, type aliases improve call sites but not use sites. Still a clear improvement overall.
  2. Great.
Contributor

cbeust commented Apr 21, 2016

@dnpetrov

Thanks for the clarifications.

  1. It's a bit unfortunate since it weakens type safety (I can pass an Int to a function expecting a Dimension) but I understand that having to wrap all these cases would be detrimental both at the source level (forced to wrap everything) and possibly at the bytecode level (necessary boxing, although that could probably be avoided). In other words, type aliases improve call sites but not use sites. Still a clear improvement overall.
  2. Great.
@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Apr 21, 2016

Contributor

@Spasi @soywiz @cbeust
Type aliases are exactly what their name says: a new name for an existing type.

We are going to implement "new types" using value types.
Main reason: resolution rules (members win, and so on).

Initially I though about adding something like

typealias Ubyte = private Int // assignment-compatible in file scope
fun Int.toUbyte(): Ubyte = this
fun Ubyte.toInt(): Int = this
fun Ubyte.plus(that: Ubyte) = ...

This adds even more complexity to resolution rules (which are already quite complex in Kotlin),
and, again, can't be enforced for Java code (and, unlike clean, nice, well-designed languages with hopes of creating a brave new world, we have to play well with big ugly mixed-language projects).

Contributor

dnpetrov commented Apr 21, 2016

@Spasi @soywiz @cbeust
Type aliases are exactly what their name says: a new name for an existing type.

We are going to implement "new types" using value types.
Main reason: resolution rules (members win, and so on).

Initially I though about adding something like

typealias Ubyte = private Int // assignment-compatible in file scope
fun Int.toUbyte(): Ubyte = this
fun Ubyte.toInt(): Int = this
fun Ubyte.plus(that: Ubyte) = ...

This adds even more complexity to resolution rules (which are already quite complex in Kotlin),
and, again, can't be enforced for Java code (and, unlike clean, nice, well-designed languages with hopes of creating a brave new world, we have to play well with big ugly mixed-language projects).

@Spasi

This comment has been minimized.

Show comment
Hide comment
@Spasi

Spasi Apr 21, 2016

We are going to implement "new types" using value types.

As in Project Valhalla value types?

This adds even more complexity to resolution rules (which are already quite complex in Kotlin),
and, again, can't be enforced for Java code (and, unlike clean, nice, well-designed languages with hopes of creating a brave new world, we have to play well with big ugly mixed-language projects).

Sure, Java interop is important. But it's still not clear to me how having assignment incompatibility is any more complex than the current typealias proposal. My suggestion is (maybe optionally, with a new keyword/modifier):

  1. kotlinc should not let you assign a value of an unabbreviated type to an otherwise compatible abbreviated type, without an explicit cast.
  2. The IDE plugin should not list values of the unabbreviated type when you invoke auto-complete on an abbreviated type.

Java would always see the unabbreviated types and would allow (1), but that's not any worse than the current typealias proposal. Kotlin code would still benefit and if at some point Java introduces a similar concept (e.g. via value types), then interop would improve as well.

Another question I had, not sure if the current proposal covers it: Lets say I have the following:

class A
typealias B = A

fun B.foo() = ...

Since A and B are complete equivalent, is A().foo() valid code or would foo only be available to B variables?

Spasi commented Apr 21, 2016

We are going to implement "new types" using value types.

As in Project Valhalla value types?

This adds even more complexity to resolution rules (which are already quite complex in Kotlin),
and, again, can't be enforced for Java code (and, unlike clean, nice, well-designed languages with hopes of creating a brave new world, we have to play well with big ugly mixed-language projects).

Sure, Java interop is important. But it's still not clear to me how having assignment incompatibility is any more complex than the current typealias proposal. My suggestion is (maybe optionally, with a new keyword/modifier):

  1. kotlinc should not let you assign a value of an unabbreviated type to an otherwise compatible abbreviated type, without an explicit cast.
  2. The IDE plugin should not list values of the unabbreviated type when you invoke auto-complete on an abbreviated type.

Java would always see the unabbreviated types and would allow (1), but that's not any worse than the current typealias proposal. Kotlin code would still benefit and if at some point Java introduces a similar concept (e.g. via value types), then interop would improve as well.

Another question I had, not sure if the current proposal covers it: Lets say I have the following:

class A
typealias B = A

fun B.foo() = ...

Since A and B are complete equivalent, is A().foo() valid code or would foo only be available to B variables?

@abreslav

This comment has been minimized.

Show comment
Hide comment
@abreslav

abreslav Apr 21, 2016

Contributor

@Spasi Our reasoning is:

  1. Assignment-compatible type aliases will definitely be supported, because they are needed in popular use cases (naming function types and long generic types)
  2. Value classes will be supported (at some point, closer to Valhalla release), because they enable many important use cases
  3. There's no point in having two ways of expressing assignment-incompatible type aliases (newtype and value classes), so we don't want to support newtype.

@dnpetrov I think it makes sense to reflect this in the document.

Contributor

abreslav commented Apr 21, 2016

@Spasi Our reasoning is:

  1. Assignment-compatible type aliases will definitely be supported, because they are needed in popular use cases (naming function types and long generic types)
  2. Value classes will be supported (at some point, closer to Valhalla release), because they enable many important use cases
  3. There's no point in having two ways of expressing assignment-incompatible type aliases (newtype and value classes), so we don't want to support newtype.

@dnpetrov I think it makes sense to reflect this in the document.

dnpetrov added a commit that referenced this issue Apr 25, 2016

#4: Proposal for type aliases:
- type aliases and declaration conflicts
- nested types & overrides
- type projections in arguments of generic type aliases
- metadata
- minor fixes and clarifications
@VladimirReshetnikov

This comment has been minimized.

Show comment
Hide comment
@VladimirReshetnikov

VladimirReshetnikov Apr 28, 2016

NB private type aliases declared in interfaces are visible inside private methods of the corresponding interface only.

What's the reason for this restriction? Why private type aliases cannot be used, for example, in a body of a public default method? Was it supposed to say instead that the use of private type aliases in method signatures is restricted to private methods?

Even then, it looks reasonable to allow use of private type aliases in signatures of public methods of private classes nested in the interface. So, just usual visibility constraints are enforced.

Moreover, I would not want to say that private type aliases are not visible in signatures of public methods of the interface (that would mean the identifier could be bound to some other type with this name from an outer scope). I would rather say that they are visible throughout the whole interface declaration (or only throughout its body?), but their use in a signature causes an error if visibility constraints are violated.

VladimirReshetnikov commented Apr 28, 2016

NB private type aliases declared in interfaces are visible inside private methods of the corresponding interface only.

What's the reason for this restriction? Why private type aliases cannot be used, for example, in a body of a public default method? Was it supposed to say instead that the use of private type aliases in method signatures is restricted to private methods?

Even then, it looks reasonable to allow use of private type aliases in signatures of public methods of private classes nested in the interface. So, just usual visibility constraints are enforced.

Moreover, I would not want to say that private type aliases are not visible in signatures of public methods of the interface (that would mean the identifier could be bound to some other type with this name from an outer scope). I would rather say that they are visible throughout the whole interface declaration (or only throughout its body?), but their use in a signature causes an error if visibility constraints are violated.

@VladimirReshetnikov

This comment has been minimized.

Show comment
Hide comment
@VladimirReshetnikov

VladimirReshetnikov Apr 28, 2016

Question: If a generic type alias is bound to a generic class having a companion object (e.g. typealias E<T> = Enum<T>), and I want to use the alias to access a member of the companion object, can I use the alias without type arguments (e.g. E.foo()) ? If a non-generic type alias is bound to an instantiation of a generic class having a companion object (e.g. typealias M = Enum<MyEnum>), can I use the alias to access a member of the companion object? If yes, are type arguments provided in the alias declaration simply ignored in this case?

VladimirReshetnikov commented Apr 28, 2016

Question: If a generic type alias is bound to a generic class having a companion object (e.g. typealias E<T> = Enum<T>), and I want to use the alias to access a member of the companion object, can I use the alias without type arguments (e.g. E.foo()) ? If a non-generic type alias is bound to an instantiation of a generic class having a companion object (e.g. typealias M = Enum<MyEnum>), can I use the alias to access a member of the companion object? If yes, are type arguments provided in the alias declaration simply ignored in this case?

@VladimirReshetnikov

This comment has been minimized.

Show comment
Hide comment
@VladimirReshetnikov

VladimirReshetnikov Apr 28, 2016

Are annotations on type alias declarations going to be supported?

Are annotations on type alias declarations going to be supported?

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Apr 29, 2016

Contributor

@VladimirReshetnikov (private type aliases in interfaces)

What's the reason for this restriction?

That's not really a restriction, just a wrong comment. Removed.

Contributor

dnpetrov commented Apr 29, 2016

@VladimirReshetnikov (private type aliases in interfaces)

What's the reason for this restriction?

That's not really a restriction, just a wrong comment. Removed.

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Apr 29, 2016

Contributor

@VladimirReshetnikov (re type alias & companion object)
Yes, that should work the same way it does for classes. I'll add that to a proposal.

Contributor

dnpetrov commented Apr 29, 2016

@VladimirReshetnikov (re type alias & companion object)
Yes, that should work the same way it does for classes. I'll add that to a proposal.

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Apr 29, 2016

Contributor

@VladimirReshetnikov

Are annotations on type alias declarations going to be supported?

Yes. Just like annotations on properties, they'll be stored on synthetic methods in JVM binaries.

Contributor

dnpetrov commented Apr 29, 2016

@VladimirReshetnikov

Are annotations on type alias declarations going to be supported?

Yes. Just like annotations on properties, they'll be stored on synthetic methods in JVM binaries.

@tommyettinger

This comment has been minimized.

Show comment
Hide comment
@tommyettinger

tommyettinger May 10, 2016

I'm just evaluating Kotlin currently as a tool for Android and desktop game development, and Kotlin seems like a worthwhile thing to learn. A lack of aliases in Java was a source of some trouble in an earlier stage of one of my game development libraries, where memory consumption was a key concern. If Java had a way to treat an int as multiple 8-bit components (mentioned earlier for RGBA colors, I was going to use it for 2D and 3D points) in a way that had minimal overhead at runtime, a substantial amount of work (and memory) could have been saved in that project. So I'm very much in favor of Kotlin gaining this feature.

Currently, I'm... somewhat troubled by how primitive arrays with >= 2 dimensions appear to work in Kotlin, and I saw some earlier discussion that mentioned type aliases as a potential solution to the verbosity of e.g. Array<Array<IntArray>>. I know Kotlin does excellent optimizations on 1D arrays to make iteration code familiar to write and yet not allocate an Iterator, but I have no idea if that optimization persists when a normal Array contains an optimized IntArray. The underlying type in the JVM's internal representation there would preferably be [[[I anyway, where an IntArray presumably corresponds closely to [I. It seems like type aliases or a related feature could be used to take the "mapping" that IntArray, CharArray, and so on use, and extend it more easily to higher-dimensional primitive arrays. Is this a reasonable possibility?

I'm personally not concerned with a loss in type safety caused by an alias being equivalent to the type it is an alias of. Being able to get components for destructuring out of primitive types that had an alias would be simply amazing, though. I am a little concerned about multi-dimensional arrays in the current release, but seeing how quickly Kotlin has evolved, and continues to evolve, has me feeling more comfortable in starting to write Kotlin now and adapting to any changes in Array/alias terminology later.

I'm just evaluating Kotlin currently as a tool for Android and desktop game development, and Kotlin seems like a worthwhile thing to learn. A lack of aliases in Java was a source of some trouble in an earlier stage of one of my game development libraries, where memory consumption was a key concern. If Java had a way to treat an int as multiple 8-bit components (mentioned earlier for RGBA colors, I was going to use it for 2D and 3D points) in a way that had minimal overhead at runtime, a substantial amount of work (and memory) could have been saved in that project. So I'm very much in favor of Kotlin gaining this feature.

Currently, I'm... somewhat troubled by how primitive arrays with >= 2 dimensions appear to work in Kotlin, and I saw some earlier discussion that mentioned type aliases as a potential solution to the verbosity of e.g. Array<Array<IntArray>>. I know Kotlin does excellent optimizations on 1D arrays to make iteration code familiar to write and yet not allocate an Iterator, but I have no idea if that optimization persists when a normal Array contains an optimized IntArray. The underlying type in the JVM's internal representation there would preferably be [[[I anyway, where an IntArray presumably corresponds closely to [I. It seems like type aliases or a related feature could be used to take the "mapping" that IntArray, CharArray, and so on use, and extend it more easily to higher-dimensional primitive arrays. Is this a reasonable possibility?

I'm personally not concerned with a loss in type safety caused by an alias being equivalent to the type it is an alias of. Being able to get components for destructuring out of primitive types that had an alias would be simply amazing, though. I am a little concerned about multi-dimensional arrays in the current release, but seeing how quickly Kotlin has evolved, and continues to evolve, has me feeling more comfortable in starting to write Kotlin now and adapting to any changes in Array/alias terminology later.

@abreslav

This comment has been minimized.

Show comment
Hide comment
@abreslav

abreslav May 10, 2016

Contributor

It seems like type aliases or a related feature could be used to take the "mapping" that IntArray, CharArray, and so on use, and extend it more easily to higher-dimensional primitive arrays. Is this a reasonable possibility?

I can't say I can follow the question.

A note on arrays: if we care about performance, it seems prudent to have a multi-dimensional array implemented over a singe (big) IntArray, not a jagged array that allocates multiple unnecessary objects... But his is off topic already.

Contributor

abreslav commented May 10, 2016

It seems like type aliases or a related feature could be used to take the "mapping" that IntArray, CharArray, and so on use, and extend it more easily to higher-dimensional primitive arrays. Is this a reasonable possibility?

I can't say I can follow the question.

A note on arrays: if we care about performance, it seems prudent to have a multi-dimensional array implemented over a singe (big) IntArray, not a jagged array that allocates multiple unnecessary objects... But his is off topic already.

dnpetrov added a commit that referenced this issue May 24, 2016

#4: Proposal for type aliases:
- type aliases and declaration conflicts
- nested types & overrides
- type projections in arguments of generic type aliases
- metadata
- minor fixes and clarifications

dnpetrov added a commit that referenced this issue May 24, 2016

#4: Proposal for type aliases:
- remove confusing comment regarding visibility
- generic type aliases and companion objects

@bashor bashor added the language label Jun 6, 2016

@norswap

This comment has been minimized.

Show comment
Hide comment
@norswap

norswap Jun 16, 2016

Looks great. A few questions, nothing major:

  • What is the rationale of this restriction?
    typealias Second<T1, T2> = T2 // Error: type alias expands to a type parameter
  • Regarding the Array2D example, I feel like maybe a good solution instead of defining a new class could be to define the alias as: typealias Array2D<T> = Array<out Array<out T>>, hence at least eliminating the problem shown in the example. The criticism seems weak (" it exposes representation of some high-level concept"): this is what many type aliases will do, including some in this very document (e.g. FilesTable).

norswap commented Jun 16, 2016

Looks great. A few questions, nothing major:

  • What is the rationale of this restriction?
    typealias Second<T1, T2> = T2 // Error: type alias expands to a type parameter
  • Regarding the Array2D example, I feel like maybe a good solution instead of defining a new class could be to define the alias as: typealias Array2D<T> = Array<out Array<out T>>, hence at least eliminating the problem shown in the example. The criticism seems weak (" it exposes representation of some high-level concept"): this is what many type aliases will do, including some in this very document (e.g. FilesTable).
@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Jun 16, 2016

Contributor

@norswap
In fact, these two are connected.
Main reason is that we would like to keep non-denotable types (existential types in both cases) non-denotable for the time being.
Possibly should provide a more detailed explanation in the next iteration.

Contributor

dnpetrov commented Jun 16, 2016

@norswap
In fact, these two are connected.
Main reason is that we would like to keep non-denotable types (existential types in both cases) non-denotable for the time being.
Possibly should provide a more detailed explanation in the next iteration.

@norswap

This comment has been minimized.

Show comment
Hide comment
@norswap

norswap Jun 16, 2016

Aren't all types with an out annotation existential already? And you can denote them: you can write Array<out Array<out T>>. Unless by denotable you mean reifiable?

I feel like I'm missing something.

norswap commented Jun 16, 2016

Aren't all types with an out annotation existential already? And you can denote them: you can write Array<out Array<out T>>. Unless by denotable you mean reifiable?

I feel like I'm missing something.

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Jun 16, 2016

Contributor

By "denotable" I mean representable in the source code.

Types with projection arguments are a rather limited case of existential types (an existentially quantified type variable stands for the projection argument itself, and can't be used anywhere else in the type under quantifiers).

Contributor

dnpetrov commented Jun 16, 2016

By "denotable" I mean representable in the source code.

Types with projection arguments are a rather limited case of existential types (an existentially quantified type variable stands for the projection argument itself, and can't be used anywhere else in the type under quantifiers).

@norswap

This comment has been minimized.

Show comment
Hide comment
@norswap

norswap Jun 16, 2016

I don't quite follow.

In Second<T1, T2>, how would it make some type non-denotable? T2 is already denotable (by writing T2).

Ah, but maybe you mean that if we write Second<´T2, out T2>, this now denotes out T2 (well, rather, a specific - and unknown - subclass of T2), something which wasn't possible earlier. Is that it?

norswap commented Jun 16, 2016

I don't quite follow.

In Second<T1, T2>, how would it make some type non-denotable? T2 is already denotable (by writing T2).

Ah, but maybe you mean that if we write Second<´T2, out T2>, this now denotes out T2 (well, rather, a specific - and unknown - subclass of T2), something which wasn't possible earlier. Is that it?

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Jun 17, 2016

Contributor

You can (special exercise for an extra credit).
This restriction is conservative (it could be "can't expand to a captured type parameter"). Still, we don't see any use cases for type aliases that project their type parameters. Higher-kinded type aliases that could potentially use this feature are definitely not something we want in a language.

Contributor

dnpetrov commented Jun 17, 2016

You can (special exercise for an extra credit).
This restriction is conservative (it could be "can't expand to a captured type parameter"). Still, we don't see any use cases for type aliases that project their type parameters. Higher-kinded type aliases that could potentially use this feature are definitely not something we want in a language.

@alex-bel

This comment has been minimized.

Show comment
Hide comment
@alex-bel

alex-bel Jun 20, 2016

Is it possible to define an alias for a "something that implements several interfaces"?

User case: replace generics in function:
fun <T> test(obj: T) where T: Cloneable, T: Externalizable { }

with typealias

typealias CloneableAndExternalizable = Cloneable & Externalizable //How to to this?

fun test(obj: CloneableAndExternalizable) { }

Is it possible to define an alias for a "something that implements several interfaces"?

User case: replace generics in function:
fun <T> test(obj: T) where T: Cloneable, T: Externalizable { }

with typealias

typealias CloneableAndExternalizable = Cloneable & Externalizable //How to to this?

fun test(obj: CloneableAndExternalizable) { }
@abreslav

This comment has been minimized.

Show comment
Hide comment
@abreslav

abreslav Jun 22, 2016

Contributor

From Slack: https://kotlinlang.slack.com/archives/general/p1465583601001372

Evan Nelson [9:32 PM]
I don't want to take a ​_specific_​ function as a parameter. I want to take ​_any_​ function that meets a signature
And I want to be able to name that signature
So that I can distinguish between two different actions that happen to share a signature
This would give me brevity for long signatures, and also allow for better dependency injection
Specifically regarding dependency injection, suppose I have a class Calculator(add: (Int, Int) -> Int, subtract: (Int, Int) -> Int). I have no way of registered two different functions for add and subtract
But I could if I had a delegate int Add(int x, int x) and delegate int Subtract(int x, int y), as the C# syntax goes
Then my class would be class Calculator(add: Add, subtract: Subtract)
I'm guessing that type aliases will solve the brevity problem, but won't solve the dependency injection problem

This is a use case for having decent reflection access to type aliases in signatures, for DI

Contributor

abreslav commented Jun 22, 2016

From Slack: https://kotlinlang.slack.com/archives/general/p1465583601001372

Evan Nelson [9:32 PM]
I don't want to take a ​_specific_​ function as a parameter. I want to take ​_any_​ function that meets a signature
And I want to be able to name that signature
So that I can distinguish between two different actions that happen to share a signature
This would give me brevity for long signatures, and also allow for better dependency injection
Specifically regarding dependency injection, suppose I have a class Calculator(add: (Int, Int) -> Int, subtract: (Int, Int) -> Int). I have no way of registered two different functions for add and subtract
But I could if I had a delegate int Add(int x, int x) and delegate int Subtract(int x, int y), as the C# syntax goes
Then my class would be class Calculator(add: Add, subtract: Subtract)
I'm guessing that type aliases will solve the brevity problem, but won't solve the dependency injection problem

This is a use case for having decent reflection access to type aliases in signatures, for DI

@norswap

This comment has been minimized.

Show comment
Hide comment
@norswap

norswap Jun 22, 2016

@abreslav True, but isn't newtype (deliberately not in scope at the moment) the better way to do that, ultimately?

norswap commented Jun 22, 2016

@abreslav True, but isn't newtype (deliberately not in scope at the moment) the better way to do that, ultimately?

@ilya-g

This comment has been minimized.

Show comment
Hide comment
@ilya-g

ilya-g Jun 28, 2016

Member

Type aliases can't be exposed by other class or package members with more permissive visibility.

This seems rather restrictive.
Suppose one have introduced an internal/private type alias to abbreviate some long type, and wants to use this type alias in a public member signature so that an underlying long type is exposed instead.
Is there strong arguments to prohibit this use case?

Member

ilya-g commented Jun 28, 2016

Type aliases can't be exposed by other class or package members with more permissive visibility.

This seems rather restrictive.
Suppose one have introduced an internal/private type alias to abbreviate some long type, and wants to use this type alias in a public member signature so that an underlying long type is exposed instead.
Is there strong arguments to prohibit this use case?

@Groostav

This comment has been minimized.

Show comment
Hide comment
@Groostav

Groostav Jul 5, 2016

This is a use case for having decent reflection access to type aliases in signatures, for DI

this.

Can I also bring up my issue with Kotlin, SAM conversions, and AssistedInject here?

Guice has an impossibly clever mechanism to allow you to resolve runtime dependencies, I'll leave you to read up on its use here.

This has evolved into an idiom for us:

class SomeSubComponent
@Inject constructor(
    val whatever: ImportantService,
    @Assisted val runtimeValue: Int
){

  interface Factory{
    fun create(runtimeValue: Int): SomeSubComponent
  }
  //no expliciut implementations of this interface exist as per Guice's assisted inject.
  //the only implementations are generated by guice, at runtime, through dynamic proxies.

  //...
}

class SomeParentComponent
@Inject constructor(
    val anotherService: AnotherService,
    val subFactory: SomeSubComponent.Factory
)

which is nice for production, but a pain for testing, because now I cannot use lambdas to implement the Factory interface, since kotlin wont do SAM conversions for kotlin-to-kotlin interfaces.

consider the test fixture for SomeParentComponent:

SomeParentComponentFixture{

  @Test fun `when x should do z`(){
    val componentUnderTest = SomeParentComponent(
      mockOrOtherwiseResolve<AnotherService>(),
//    { alpha -> mockOrOtherwiseResolve<SomeSubComponent>()} -- illegal, cannot SAM the interface 'Factory'
//    object : SomeSubComponent.Factory { -- ugly! anonymous object syntax is never nice!
      TODO("what can we put here?")
    )
  }
}

I would greatly appreciate it if I could alias the type:

class SomeSubComponent
@Inject constructor(
    val whatever: ImportantService,
    @Assisted val runtimeValue: Int
){

  typealias: (Int) -> SomeSubComponent as Factory

  //...
}

such that I can use simple lambda's from tests:

SomeParentComponentFixture{

  @Test fun `when x should do z`(){
    val componentUnderTest = SomeParentComponent(
      mockOrOtherwiseResolve<AnotherService>(),
      { alpha -> mockOrOtherwiseResolve<SomeSubComponent>()} // Legal, lamba is aliased as Factory!
  }
}

but then it would need to be exportable:

class SomeParentComponent
@Inject constructor(
    val anotherService: AnotherService,
    val subFactory: SomeSubComponent.Factory //??
)

//and

class AModule : Module{
  override fun configure(){
    bind(SomeSubComponent.Factory::class) //???
        .asAssistedInjectThing()
  }
}

as it is I'm currently using the anonymous object syntax in all my tests, and either I extract a method for it, making my tests more complex than I'd like, or I let the copy-pasta flow, which I'm completely opposed to.

This is a kotlin side fix for google/guice#1010

Groostav commented Jul 5, 2016

This is a use case for having decent reflection access to type aliases in signatures, for DI

this.

Can I also bring up my issue with Kotlin, SAM conversions, and AssistedInject here?

Guice has an impossibly clever mechanism to allow you to resolve runtime dependencies, I'll leave you to read up on its use here.

This has evolved into an idiom for us:

class SomeSubComponent
@Inject constructor(
    val whatever: ImportantService,
    @Assisted val runtimeValue: Int
){

  interface Factory{
    fun create(runtimeValue: Int): SomeSubComponent
  }
  //no expliciut implementations of this interface exist as per Guice's assisted inject.
  //the only implementations are generated by guice, at runtime, through dynamic proxies.

  //...
}

class SomeParentComponent
@Inject constructor(
    val anotherService: AnotherService,
    val subFactory: SomeSubComponent.Factory
)

which is nice for production, but a pain for testing, because now I cannot use lambdas to implement the Factory interface, since kotlin wont do SAM conversions for kotlin-to-kotlin interfaces.

consider the test fixture for SomeParentComponent:

SomeParentComponentFixture{

  @Test fun `when x should do z`(){
    val componentUnderTest = SomeParentComponent(
      mockOrOtherwiseResolve<AnotherService>(),
//    { alpha -> mockOrOtherwiseResolve<SomeSubComponent>()} -- illegal, cannot SAM the interface 'Factory'
//    object : SomeSubComponent.Factory { -- ugly! anonymous object syntax is never nice!
      TODO("what can we put here?")
    )
  }
}

I would greatly appreciate it if I could alias the type:

class SomeSubComponent
@Inject constructor(
    val whatever: ImportantService,
    @Assisted val runtimeValue: Int
){

  typealias: (Int) -> SomeSubComponent as Factory

  //...
}

such that I can use simple lambda's from tests:

SomeParentComponentFixture{

  @Test fun `when x should do z`(){
    val componentUnderTest = SomeParentComponent(
      mockOrOtherwiseResolve<AnotherService>(),
      { alpha -> mockOrOtherwiseResolve<SomeSubComponent>()} // Legal, lamba is aliased as Factory!
  }
}

but then it would need to be exportable:

class SomeParentComponent
@Inject constructor(
    val anotherService: AnotherService,
    val subFactory: SomeSubComponent.Factory //??
)

//and

class AModule : Module{
  override fun configure(){
    bind(SomeSubComponent.Factory::class) //???
        .asAssistedInjectThing()
  }
}

as it is I'm currently using the anonymous object syntax in all my tests, and either I extract a method for it, making my tests more complex than I'd like, or I let the copy-pasta flow, which I'm completely opposed to.

This is a kotlin side fix for google/guice#1010

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Jul 5, 2016

Contributor

@alex-bel

Is it possible to define an alias for a "something that implements several interfaces"?

No, unless we decide to make intersection types denotable. This is a rather big can of worms, although it MAY become possible after type inference improvements planned for 1.1.

Contributor

dnpetrov commented Jul 5, 2016

@alex-bel

Is it possible to define an alias for a "something that implements several interfaces"?

No, unless we decide to make intersection types denotable. This is a rather big can of worms, although it MAY become possible after type inference improvements planned for 1.1.

@kareez

This comment has been minimized.

Show comment
Hide comment
@kareez

kareez Jul 24, 2016

Love the feature, just applied it to one of my projects and can see the impact in readability and the code size.

Just a minor feedback, Since I am a little too much picky about the names, typealias seems too long and besides it is not a single word but two. what about "alias" only?

kareez commented Jul 24, 2016

Love the feature, just applied it to one of my projects and can see the impact in readability and the code size.

Just a minor feedback, Since I am a little too much picky about the names, typealias seems too long and besides it is not a single word but two. what about "alias" only?

@bentolor

This comment has been minimized.

Show comment
Hide comment
@bentolor

bentolor Aug 1, 2016

After all the positive feedback yet, sorry for posting a more sceptical/critical feedback. My core question is: What are the key benefits of type aliases?

From what I've read so far I feel, that type aliases do make it harder and more cumbersome to understand code by plain reading:

  • While reading code, one can no longer distinguish references to real types from arbitrarily synonyms. This makes it IMHO much harder to understand code w/o tool support.
  • It undermines the expectations regarding the guarantees the type system gives me.
    • Until now If I read var p: Person? = null; var m: Male = aMale; p=m; I know for sure that Male : Person.
    • With type aliases p could even be just Object.
    • And suddenly p: Person = 1.toDouble() and d: Double = p can work against every intuition
  • In Java locating symbols is really fool-proof. In Kotlin it is already a little harder
    • If you see a foo.model.Person.doThing() method call in Java you know exactly where to look for the implementation. Ok, unless you inherit. But even then the path is clear.
    • Kotlin already breaks this convention by decoupling the package & class names from file names. I'm not a fan of this approach, but I can live with that. Will tell people the same thing what I do in .NET: "Keep names & files aligned!"
    • Extension methods are a great tool and bring much added value. But again they make it harder to know, where to look for p.doFoo(). Given the huge benefit to be able to extend library elements, I'm bought and fine with them, too. Though it gives the developer more choice and more responsibility on placing functionality in the right place and scope
    • With type aliases another mechanism would be introduced to globally decouple symbol names from their real implementation place. In contrast to extension methods, I really do miss the real benefit: Less typing in IDEA? Really? Saving "overhead" in a interpreted, typed, null-checked environment?

In my opinion the sweet spot of Kotlin in stark contrast to i.e. Scale et. al is, that it adds valuably new functionality while keeping much of the simplicity of Java and even making some parts of it easier (Nullability, Generics handling, ...).

For me type aliases sound a little like a neat language toy thrilling the language enthusiast, but horrifying everyday developers by making it even harder to understand the language and code.

Please convince me ;-)

bentolor commented Aug 1, 2016

After all the positive feedback yet, sorry for posting a more sceptical/critical feedback. My core question is: What are the key benefits of type aliases?

From what I've read so far I feel, that type aliases do make it harder and more cumbersome to understand code by plain reading:

  • While reading code, one can no longer distinguish references to real types from arbitrarily synonyms. This makes it IMHO much harder to understand code w/o tool support.
  • It undermines the expectations regarding the guarantees the type system gives me.
    • Until now If I read var p: Person? = null; var m: Male = aMale; p=m; I know for sure that Male : Person.
    • With type aliases p could even be just Object.
    • And suddenly p: Person = 1.toDouble() and d: Double = p can work against every intuition
  • In Java locating symbols is really fool-proof. In Kotlin it is already a little harder
    • If you see a foo.model.Person.doThing() method call in Java you know exactly where to look for the implementation. Ok, unless you inherit. But even then the path is clear.
    • Kotlin already breaks this convention by decoupling the package & class names from file names. I'm not a fan of this approach, but I can live with that. Will tell people the same thing what I do in .NET: "Keep names & files aligned!"
    • Extension methods are a great tool and bring much added value. But again they make it harder to know, where to look for p.doFoo(). Given the huge benefit to be able to extend library elements, I'm bought and fine with them, too. Though it gives the developer more choice and more responsibility on placing functionality in the right place and scope
    • With type aliases another mechanism would be introduced to globally decouple symbol names from their real implementation place. In contrast to extension methods, I really do miss the real benefit: Less typing in IDEA? Really? Saving "overhead" in a interpreted, typed, null-checked environment?

In my opinion the sweet spot of Kotlin in stark contrast to i.e. Scale et. al is, that it adds valuably new functionality while keeping much of the simplicity of Java and even making some parts of it easier (Nullability, Generics handling, ...).

For me type aliases sound a little like a neat language toy thrilling the language enthusiast, but horrifying everyday developers by making it even harder to understand the language and code.

Please convince me ;-)

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Aug 1, 2016

Contributor

Key benefit for type aliases: do not repeat yourself.
Type aliases allow you to introduce domain-specific names for types without introducing new classes.

Now, some minor nit-picking and biased comments.

With type aliases p could even be just Object

Only if Person is defined that way (typealias Person = Any).
Note that you can abuse almost any high-level abstraction by providing inadequate definitions.

And suddenly p: Person = 1.toDouble() and d: Double = p can work against every intuition

If Person is a type alias for Any, d: Double = p will not type check.

In Java locating symbols is really fool-proof. In Kotlin it is already a little harder

...and that is handled quite well by mature IDEs.

Contributor

dnpetrov commented Aug 1, 2016

Key benefit for type aliases: do not repeat yourself.
Type aliases allow you to introduce domain-specific names for types without introducing new classes.

Now, some minor nit-picking and biased comments.

With type aliases p could even be just Object

Only if Person is defined that way (typealias Person = Any).
Note that you can abuse almost any high-level abstraction by providing inadequate definitions.

And suddenly p: Person = 1.toDouble() and d: Double = p can work against every intuition

If Person is a type alias for Any, d: Double = p will not type check.

In Java locating symbols is really fool-proof. In Kotlin it is already a little harder

...and that is handled quite well by mature IDEs.

@abreslav

This comment has been minimized.

Show comment
Hide comment
@abreslav

abreslav Aug 9, 2016

Contributor

True, but isn't newtype (deliberately not in scope at the moment) the better way to do that, ultimately?

@norswap I can't see how it is better. AFAIU, it doesn't even solve the problem, because the use case mentions abbreviation, for which newtype is a poor fit

Contributor

abreslav commented Aug 9, 2016

True, but isn't newtype (deliberately not in scope at the moment) the better way to do that, ultimately?

@norswap I can't see how it is better. AFAIU, it doesn't even solve the problem, because the use case mentions abbreviation, for which newtype is a poor fit

@abreslav

This comment has been minimized.

Show comment
Hide comment
@abreslav

abreslav Aug 9, 2016

Contributor

@Groostav this looks like an abuse of the concept of type alias to me, but your use case is valuable, I've added a link to it to https://youtrack.jetbrains.com/issue/KT-7770

Contributor

abreslav commented Aug 9, 2016

@Groostav this looks like an abuse of the concept of type alias to me, but your use case is valuable, I've added a link to it to https://youtrack.jetbrains.com/issue/KT-7770

@norswap

This comment has been minimized.

Show comment
Hide comment
@norswap

norswap Aug 14, 2016

I tried typealias today and ran into one big issue: static Java methods are not picked up.

typealias Alias = OriginalJavaClass
fun test() {
  Alias.someStaticMethod() // compilation error
}

Since the companion object is translated for Kotlin, this should probably be fixed.

norswap commented Aug 14, 2016

I tried typealias today and ran into one big issue: static Java methods are not picked up.

typealias Alias = OriginalJavaClass
fun test() {
  Alias.someStaticMethod() // compilation error
}

Since the companion object is translated for Kotlin, this should probably be fixed.

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Aug 15, 2016

Contributor

@norswap https://youtrack.jetbrains.com/issue/KT-13161, fixed in master (will be in 1.1-M02).
If you find any other bugs, please, don't hesitate to use YouTrack.

Contributor

dnpetrov commented Aug 15, 2016

@norswap https://youtrack.jetbrains.com/issue/KT-13161, fixed in master (will be in 1.1-M02).
If you find any other bugs, please, don't hesitate to use YouTrack.

@zjhmale zjhmale referenced this issue in intellij-rust/intellij-rust Aug 26, 2016

Merged

Toolchain dedup #628

@Supuhstar

This comment has been minimized.

Show comment
Hide comment
@Supuhstar

Supuhstar Aug 30, 2016

if a discussion about syntax is still allowed, I think this would fit naturally into the language:

typealias MyType: SomeClass where MyType: SomeInterface, MyType: SomeOtherInterface<MyType>

(inspired by @alex-bel)

Supuhstar commented Aug 30, 2016

if a discussion about syntax is still allowed, I think this would fit naturally into the language:

typealias MyType: SomeClass where MyType: SomeInterface, MyType: SomeOtherInterface<MyType>

(inspired by @alex-bel)

@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Aug 30, 2016

Contributor

@Supuhstar if I read it right, it's not just syntax, but an extension to the type system - basically, making intersection types denotable. MyType is a subtype of SomeClass, SomeInterface, and SomeOtherInterface<MyType> effectively means that the corresponding type is an intersection (strawman syntax below):

typealias MyType = SomeClass & SomeInterface & SomeOtherInterface<MyType>

Type inference algorithm used in 1.0 doesn't handle intersection types quite well. They appear in some particular contexts and are approximated at some stage (and, as a result, do not "escape"). Making intersection types denotable with the current TI will allow them everywhere, thus opening a bigger can of worms.

Some improvements in TI are in progress in the 1.1 scope. One of the assumptions is that after those improvements we'll be able to make intersection types first-class entities in the type system. Only after that we'll be able to introduce denotable intersection types, and, as a result, support something like you've described above.

However, there's at least one big open question left: Java interop. It is, of cause, possible to represent intersection types in some places using constrained generics. However, it's yet to be checked whether that representation is enough to make sure that a Java part of the big multi-language project (read: IntelliJ IDEA) honors Kotlin intersection types.

Contributor

dnpetrov commented Aug 30, 2016

@Supuhstar if I read it right, it's not just syntax, but an extension to the type system - basically, making intersection types denotable. MyType is a subtype of SomeClass, SomeInterface, and SomeOtherInterface<MyType> effectively means that the corresponding type is an intersection (strawman syntax below):

typealias MyType = SomeClass & SomeInterface & SomeOtherInterface<MyType>

Type inference algorithm used in 1.0 doesn't handle intersection types quite well. They appear in some particular contexts and are approximated at some stage (and, as a result, do not "escape"). Making intersection types denotable with the current TI will allow them everywhere, thus opening a bigger can of worms.

Some improvements in TI are in progress in the 1.1 scope. One of the assumptions is that after those improvements we'll be able to make intersection types first-class entities in the type system. Only after that we'll be able to introduce denotable intersection types, and, as a result, support something like you've described above.

However, there's at least one big open question left: Java interop. It is, of cause, possible to represent intersection types in some places using constrained generics. However, it's yet to be checked whether that representation is enough to make sure that a Java part of the big multi-language project (read: IntelliJ IDEA) honors Kotlin intersection types.

@Supuhstar

This comment has been minimized.

Show comment
Hide comment
@Supuhstar

Supuhstar Aug 30, 2016

@dnpetrov oh beautiful! I can't wait :D

@dnpetrov oh beautiful! I can't wait :D

dnpetrov added a commit that referenced this issue Oct 4, 2016

KEEP #4: update type aliases proposal.
- Type aliases as qualifiers for Java static methods
- Nested type aliases & related issues
- Type aliases as super-qualifiers
- Reflection API for type aliases
- Annotations on type aliases

dnpetrov added a commit that referenced this issue Oct 4, 2016

KEEP #4, minor:
- add kotlin.SinceKotlin to the list of standard annotations that should be applicable to type aliases;
- make related text a bit more general.

@abreslav abreslav modified the milestone: 1.1 Oct 10, 2016

dnpetrov added a commit that referenced this issue Nov 9, 2016

KEEP #4:
- type aliases in interfaces can't be internal
- type inference for type alias constructor arguments

dnpetrov added a commit that referenced this issue Nov 21, 2016

KEEP #4
- Explicitly specify restrictions on abbreviated types with projections as top-level arguments - as for regular types.
- Local and nested type aliases are not supported in 1.1.
@romainguy

This comment has been minimized.

Show comment
Hide comment
@romainguy

romainguy Jan 4, 2017

Is newtype (or equivalent) definitely off the table? Being able to create new types based on primitive types is really useful for graphics work (for instance to prevent passing a linear color as a gamma encode color or to avoid mixing up radiance and irradiance, etc.).

Is newtype (or equivalent) definitely off the table? Being able to create new types based on primitive types is really useful for graphics work (for instance to prevent passing a linear color as a gamma encode color or to avoid mixing up radiance and irradiance, etc.).

@hotchemi hotchemi referenced this issue in dexfm/dex.fm Jan 25, 2017

Closed

Episode 17,18 #19

@abreslav abreslav moved this from KEEP accepted to Implemented in KEEP Kotlin 1.1 Jan 27, 2017

@smillies

This comment has been minimized.

Show comment
Hide comment
@smillies

smillies Feb 26, 2017

I have a question about type aliases when specifying a function type with receiver: Why is the first example illegal, while the second is legal?

typealias Property = () -> Boolean
val isEven: Int.Property = { this % 2 == 0 }
typealias IntPredicate = Int.() -> Boolean
val isEven: IntPredicate = { this % 2 == 0 }

I have a question about type aliases when specifying a function type with receiver: Why is the first example illegal, while the second is legal?

typealias Property = () -> Boolean
val isEven: Int.Property = { this % 2 == 0 }
typealias IntPredicate = Int.() -> Boolean
val isEven: IntPredicate = { this % 2 == 0 }
@dnpetrov

This comment has been minimized.

Show comment
Hide comment
@dnpetrov

dnpetrov Feb 27, 2017

Contributor

@smillies
Type A.B means "B in static scope of A", not "functional type B with receiver type A".
It is a coincidence that A.(B) -> C also uses .; in fact, (B) -> C here is not a separate "type argument".

Contributor

dnpetrov commented Feb 27, 2017

@smillies
Type A.B means "B in static scope of A", not "functional type B with receiver type A".
It is a coincidence that A.(B) -> C also uses .; in fact, (B) -> C here is not a separate "type argument".

@dpkirchner

This comment has been minimized.

Show comment
Hide comment
@dpkirchner

dpkirchner Mar 2, 2017

Is it possible to alias an annotated type? That is, some way to alias FooString to @Named("foo") String or @field:[Named("foo")] String ? I'd use it to make scoped/named annotations less awkward.

Is it possible to alias an annotated type? That is, some way to alias FooString to @Named("foo") String or @field:[Named("foo")] String ? I'd use it to make scoped/named annotations less awkward.

@trevjonez

This comment has been minimized.

Show comment
Hide comment
@trevjonez

trevjonez Sep 17, 2017

Would be nice to allow nested typealias for just making things read better.

say I have the following bits of code:

data class Foo(val id: UUID, /* useful properties */)
data class Bar(val id: UUID, /* useful properties */)

fun mapSomeInfo(foos: List<Foo>, bars: List<Bar>): Map<UUID, UsefulDataDerivedFromFooBar> { ... }

All is fine and dandy but I want to put more meaningful type to the key of the map result from mapSomeInfo great so now we create a few type aliases.

typealias FooId = UUID
typealias BarId = UUID

data class Foo(val id: FooId, /* useful properties */)
data class Bar(val id: BarId, /* useful properties */)

fun mapSomeInfo(foos: List<Foo>, bars: List<Bar>): Map<FooId, UsefulDataDerivedFromFooBar> { ... }

Now we have that bit of info defined and our map result is clearly key'd by ID's from Foos.

What I would like to do is take it a step further so it can become:

data class Foo(val id: Id, /* useful properties */) {
  typealias Id = UUID
}

data class Bar(val id: Id, /* useful properties */) {
  typealias Id = UUID
}

fun mapSomeInfo(foos: List<Foo>, bars: List<Bar>): Map<Foo.Id, UsefulDataDerivedFromFooBar> { ... }

My intent was always to create an alias for an ID specifically for the consuming type to enable readability. NameSpacing via nesting would give me that as well as create the association back the other direction without having to create wasteful wrapper data classes.

trevjonez commented Sep 17, 2017

Would be nice to allow nested typealias for just making things read better.

say I have the following bits of code:

data class Foo(val id: UUID, /* useful properties */)
data class Bar(val id: UUID, /* useful properties */)

fun mapSomeInfo(foos: List<Foo>, bars: List<Bar>): Map<UUID, UsefulDataDerivedFromFooBar> { ... }

All is fine and dandy but I want to put more meaningful type to the key of the map result from mapSomeInfo great so now we create a few type aliases.

typealias FooId = UUID
typealias BarId = UUID

data class Foo(val id: FooId, /* useful properties */)
data class Bar(val id: BarId, /* useful properties */)

fun mapSomeInfo(foos: List<Foo>, bars: List<Bar>): Map<FooId, UsefulDataDerivedFromFooBar> { ... }

Now we have that bit of info defined and our map result is clearly key'd by ID's from Foos.

What I would like to do is take it a step further so it can become:

data class Foo(val id: Id, /* useful properties */) {
  typealias Id = UUID
}

data class Bar(val id: Id, /* useful properties */) {
  typealias Id = UUID
}

fun mapSomeInfo(foos: List<Foo>, bars: List<Bar>): Map<Foo.Id, UsefulDataDerivedFromFooBar> { ... }

My intent was always to create an alias for an ID specifically for the consuming type to enable readability. NameSpacing via nesting would give me that as well as create the association back the other direction without having to create wasteful wrapper data classes.

@dzharkov

This comment has been minimized.

Show comment
Hide comment
@dzharkov

dzharkov Feb 14, 2018

Collaborator

The feature has been released in Kotlin 1.1, thus I'm closing the KEEP. Do not hesitate to open a YouTrack issue for any additional suggestions.

Collaborator

dzharkov commented Feb 14, 2018

The feature has been released in Kotlin 1.1, thus I'm closing the KEEP. Do not hesitate to open a YouTrack issue for any additional suggestions.

@dzharkov dzharkov closed this Feb 14, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment