Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type aliases #4

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

Type aliases #4

abreslav opened this issue Apr 20, 2016 · 46 comments
Labels
Milestone

Comments

@abreslav
Copy link
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
Copy link
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
Copy link
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
Copy link

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
Copy link

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
Copy link

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
Copy link
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.

@cbeust
Copy link
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
Copy link
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
Copy link

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
Copy link
Contributor Author

@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
- type aliases and declaration conflicts
- nested types & overrides
- type projections in arguments of generic type aliases
- metadata
- minor fixes and clarifications
@VladimirReshetnikov
Copy link

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
Copy link

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
Copy link

Are annotations on type alias declarations going to be supported?

@dnpetrov
Copy link
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
Copy link
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.

@dnpetrov
Copy link
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.

@tommyettinger
Copy link

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
Copy link
Contributor Author

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
- 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
- remove confusing comment regarding visibility
- generic type aliases and companion objects
@bashor bashor added the language label Jun 6, 2016
@norswap
Copy link

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
Copy link
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.

@norswap
Copy link

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
Copy link
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).

@norswap
Copy link

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
Copy link
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.

@norswap
Copy link

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
Copy link
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
Copy link

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
Copy link
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.

@mhshams
Copy link

mhshams 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
Copy link

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
Copy link
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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link

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
Copy link
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.

@Supuhstar
Copy link

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
Copy link
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.

@Supuhstar
Copy link

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

dnpetrov added a commit that referenced this issue Oct 4, 2016
- 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
- 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
- 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
- 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
Copy link

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.).

@smillies
Copy link

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
Copy link
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".

@dpkirchner
Copy link

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
Copy link

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
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests