Proposal: strict typealias

Hello,

Here is the proposal:

Do not allow a typealias to be assigned to another (even if it’s the same type at runtime). doing this is forbidden by compiler (resulting a compilation error).

the following example will be used:

class RealRuntimeType
typealias TypeAlias = RealRuntimeType
typealias TypeAliasBis = RealRuntimeType

note: they are typealias, this is all compilation checks & only RealRuntimeType will exist at runtime.

Why ?

This allow typealias to be use to type things in a very light way (from code point of view & runtime).

assignation

The global idea is too allow transformation (that will be lose at runtime) from an alias type to and from its real type.
so we can assign:

  • a RealRuntimeType to a TypeAlias & TypeAliasBis
  • a TypeAlias to a RealRuntimeType
  • a TypeAliasBis to a RealRuntimeType

example:

class RealRuntimeType
typealias TypeAlias = RealRuntimeType
typealias TypeAliasBis = RealRuntimeType
    
fun main(args: Array<String>) {
  val aTypeAlias: TypeAlias = RealRuntimeType() //ok
  val aTypeAliasBis: TypeAliasBis = RealRuntimeType() //ok
  val anotherTypeAliasBis: TypeAliasBis = aTypeAlias //compile error : "TypeAlias cannot be assigned to TypeAliasBis"
  afunction(aTypeAlias) //compile error : "type mismatched..."
}

fun afunction(typeAliasBis: TypeAliasBis ) = ...

as & is operator

this is just compile erorrs, at runtime we only have RealRuntimeType so any dynamic operator (as, is) is running with RealRuntimeType.

var aTypeAlias: TypeAlias = RealRuntimeType() //ok
var aTypeAliasBis: TypeAliasBis = aTypeAlias as RealRuntimeType //ok

this also means that any dynamic operation (like as & is) are not subject to the compiler checkings in this proposal):

aTypeAliasBis = aTypeAlias as TypeAliasBis //ok, at runtime they all are RealRuntimeType

Not been able to write val anotherTypeAliasBis:TypeAliasBis = aTypeAlias while we can write val anotherTypeAliasBis: TypeAliasBis = aTypeAlias as TypeAliasBis can be confusing but the code deliberatly use a dynamic operation so its ok.

Same applies to use of is and smart cast

Back compatibilty

Because this could break existing code it should be done in 2 steps : first generate a warning, then, in a later version, go for the error.

If we really want to assign a typealias to another, just explicitly cast it to the real runtime type : val y:Y = x as String

What about inline class ?

Inline class are made to add behaviour (you can add methods, implement interface…). They are very powerfull & light at runtime… But we don’t need all this power just to just type things. Moreover inline classes come with drawbacks

from a code point-of-view, if we just want to type thing:

  • Inline class are berbose at use : need to call constructor on creation & .value on pseudo destructuration (which is a problem mostly in tests).
  • Inline class are verbose at declaration if we just want to type thing : inline class X(val value:String) : the only important information is : X & String

From a runtime point-iof-view:

  • they can be (accidently) boxed
  • they cannot be used in generics without boxing
  • they have specific behaviour regarding to array
  • and so on…

I’m not saying inline class are bad (actually they are insanly powerfull), they are just not intended “just to type thing”.

Variant

Add a soft keyword before typealias to add this behaviour. Example strict typealias Y = String

This could be used as a temporary or definitive variant

2 Likes

Hi @noo.blaster,
can you include in this proposal the is and as operators, please?

Thank you.

as & is (and smartcast) are dynamic stuff : they are not subject of this proposal (trying to modify them would add strange behaviours).

still, I add a paragraph about it.

Alias is a different name for the same thing. If you need this functionality, I think it should use another keyword than typealias.

hum… I agree… Pseudotype proposal : typealias are too weak, inline classes are too verbose was better…

Thanks again @noo.blaster,
so aTypeAlias is TypeAliasBis always but I cannot assign the aTypeAlias to TypeAliasBis, it looks confused and it is a great disvantage over inline class, you should mention this in the proposal.

I’m still not really convinced this is something that should be part of kotlin. As I pointed out in your original post this proposal basically adds inline classes with implicit casts, without the advantage of adding functionality to the new types.

I understand why you might want this feature, but I think as it is so similar to inline classes we should really think about the costs this feature would add to the language. I think it will be confusing that there are so many concepts which look like they do the same thing but are still very different.
This will get even worse once the jvm supports value types, which IMO will make inline classes kind of obsolete.

If we look at the pros and cons I don’t see why this needs to be in the language. Maybe I missed something.

Pros:

  • Less verbose than inline classes because of implicit cast
  • Allows the compiler to check (in a very limited way) the intent of a programmer, a password should be a password and not an email – type aliases are not able to do so, inline classes are however
  • No runtime overhead – same as inline classes but inline classes sometimes lead to boxing (generics)

Cons:

  • adds inconsistencies to the language: (a == b && b == c but b != c), this is the same problem @fvasco pointed out. Also type checks no longer tell you whether you can assign a value.
  • Usage overlaps with inline classes ( and value types) which means that there will be 2 (3) features solving the same problem. This will lead to confusion of which of those to choose and as far as I can tell now, switching between them will not be trivial as they have subtle differences in their requirements (inline classes and value types may have member functions/properties, inheritance, syntax of how to access them, etc)
  • There is no reason why those pseudo types should not allow for member functions like inline classes do. This will be expected naturally once they exist. The type system has to know about them, so why not add functionality? I can’t think of a single example, where I would want to have a pseudo type for a light way type checking, without also wanting to add functionality (if it’s some kind of item id, query the item or encrypt/decrypt the password, etc). At that point we just have inline classes with implicit casts, which is something kotlin decided against from the start.
  • the minus 100 point rule
  • this will just lead to every library defining it’s own basic types like you see in C/C++. Why do I need a different name like GLint for int and GLvoid for void etc. And C/C++ does not even have the advantage of your proposed pseudo-types. They are just type aliases?
    Imagine combining 2 libraries, both containing a pseudo type for Password. Now you have to write password as PasswordTypeA and password as PasswordTypeB everywhere, because those types don’t match even though they are all just Strings.

I might point out that in my experience the verbosity of inline classes is not that bad if you design your code around inline classes from the start. I use them in a opengl (lwjgl) project of mine for everything from texture/window/shader/etc. In OpenGL they are all represented as an integer.
Yes it’s more work than just replacing int with Shader but if you design your program correctly this does not matter. The only place where I am more verbose is in the creation of those objects, which means yes I have to use the constructor, but this is not often, because my entire project uses Shader instead of Int. The other place is where I interface with OpenGl where I need to use shader.id instead of just shader. But this is not necessary often. Most of the time I just need to be able to compare and save the value.

3 Likes

So the difference compile time vs runtime seems too confusing (inline class does not have this problem… I’m not event sure about there behaviour toward is and as). I get it.

Having several potential to same problem seems a good argument (even if I still consider they are not same problem, but I admit they are very close, maybe too close to add a feature)

Hi Wasabi,

I’m doing exactly the same here! Any chance we might joint our efforts? :slight_smile:

I agree that It can be confusing, maybe it’s enough have a warning.

val anotherTypeAliasBis: TypeAliasBis = aTypeAlias //warning : "TypeAlias cannot be assigned to TypeAliasBis"
afunction(aTypeAlias) //warning : "type mismatched..."

Hi,
I don’t want to open a new topic about typealias. As I understand it, typealias is only globally declarable. Do you plan to make it available locally? For example:

fun foo (bar: Int) {
  typealias KeyMap = Map<Int, Bar<Int, Int, Double>>
  // do some work ...
}
2 Likes