-
Notifications
You must be signed in to change notification settings - Fork 363
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
Comments
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.:
|
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. |
Could you please explain why "newtype" is out of scope for the first iteration of type aliases? The part under 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 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 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.
Example:
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. So i want to be able to do Also this could be used for distances: Also you could create a But totally worth for allocation-free integers and doubles wrappers. |
Another use case I thought (for compile-time new types). Simulating unsigned types:
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): |
|
Thanks for the clarifications.
|
@Spasi @soywiz @cbeust We are going to implement "new types" using value types. Initially I though about adding something like
This adds even more complexity to resolution rules (which are already quite complex in Kotlin), |
As in Project Valhalla value types?
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):
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 |
@Spasi Our reasoning is:
@dnpetrov I think it makes sense to reflect this in the document. |
- type aliases and declaration conflicts - nested types & overrides - type projections in arguments of generic type aliases - metadata - minor fixes and clarifications
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. |
Question: If a generic type alias is bound to a generic class having a companion object (e.g. |
Are annotations on type alias declarations going to be supported? |
@VladimirReshetnikov (private type aliases in interfaces)
That's not really a restriction, just a wrong comment. Removed. |
@VladimirReshetnikov (re type alias & companion object) |
Yes. Just like annotations on properties, they'll be stored on synthetic methods in JVM binaries. |
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 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. 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 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) |
- type aliases and declaration conflicts - nested types & overrides - type projections in arguments of generic type aliases - metadata - minor fixes and clarifications
- remove confusing comment regarding visibility - generic type aliases and companion objects
Looks great. A few questions, nothing major:
|
@norswap |
Aren't all types with an I feel like I'm missing something. |
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). |
I don't quite follow. In Ah, but maybe you mean that if we write |
You can (special exercise for an extra credit). |
@abreslav True, but isn't |
This seems rather restrictive. |
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 consider the test fixture for 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 |
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. |
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? |
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:
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 ;-) |
Key benefit for type aliases: do not repeat yourself. Now, some minor nit-picking and biased comments.
Only if
If
...and that is handled quite well by mature IDEs. |
@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 |
@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 |
I tried typealias today and ran into one big issue: static Java methods are not picked up.
Since the companion object is translated for Kotlin, this should probably be fixed. |
@norswap https://youtrack.jetbrains.com/issue/KT-13161, fixed in master (will be in 1.1-M02). |
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 if I read it right, it's not just syntax, but an extension to the type system - basically, making intersection types denotable.
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. |
@dnpetrov oh beautiful! I can't wait :D |
- 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
Is |
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?
|
@smillies |
Is it possible to alias an annotated type? That is, some way to alias |
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 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. |
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. |
Discussions about the Type aliases proposal will be held here.
@dnpetrov is the shepherd for this proposal.
The text was updated successfully, but these errors were encountered: