Currently, generics are translated using erasure. This means, that generics can only be instantiated with types compatible with int. The idea of this proposal is to change the translation and instead of using erasure translation would generate one copy per instantiated type. This would allow to extend generics with some interesting new features, but would also be incompatible in some cases.
Language changes:
1. Allow all types for generics
Every type can be inserted for a generic type parameter without any restrictions.
For example, we can then use LinkedList<vec3>.
This also allows us to get rid of the implicit calls to fromIndex and toIndex that we currently have as a woraround.
2. Allow to restrict type parameters / type classes
Sometimes we want to restrict type parameters.
For example LinkedList<T>.toString() returns string only can be implemented, if we have a method toString(T) returns string.
We could of course provide this method using an additional argument:
LinkedList<T>.toString(Show<T> elementShower) returnsstring
... elementShower.toString(x) ...
interfaceShow<T>
functiontoString(Telem) returnsstring
However, it is not very convenient to always pass this Showvalue explicitly.
Instead we allow to use generic interfaces as type constraints:
functionLinkedList<T>.toString<T : Show>() returnsstring
... T.toString(x) ...
interfaceSerializable<T>
functionencode(Tt) returnsstringfunctiondecode(strings) returnsTfunctionLinkedList<T>.encode<T : Serializable>() returnsstring
...
functionLinkedList<T>.decode<T : Serializable>(strings) returnsT
...
The compiler automatically finds instances that are defined with the new instance definitions:
instanceShow<vec2>
functiontoString(vec2v) returnsstringreturn"(" + v.x.toString() + ", " + v.y.toString() + ")"// make all lists with serializable elements serializable:instance <T : Serializable> implementsSerializable<LinkedList<T>>
// implement methods hereIf multiple instance are in scope there is a compilation error.
The instantiation is part of the type, so the following example is not allowed:
packageApublicinterfaceOrd<T>
functionlessEq(Ta, Tb) returnsbooleaninstanceOrd<string>
functionlessEq(stringa, stringb) returnsbooleanreturna.length() <= b.length()
publicclassTreeSet<T : Ord>
...
publicTreeSet<string> mySet = TreeSet.create("a","aa","aaa")
packageBimportAinstanceOrd<string>
functionlessEq(stringa, stringb) returnsbooleanreturna.length() >= b.length()
initTreeSet<string> s = mySet// error, because Ord intances are not compatibleTo resolve instances, we first match the parameters and then match the type constraints from left to right.
Type class translation: The translation uses monomorphization (like traits in Rust) when translating to Jass and dictionary passing when translating to Lua.
3. Disallow casting to int
Since we now allow all types to be used for type parameters, we can no longer cast type parameters to int. However, we can use the new feature of restricted type parameters to implement the same feature:
Old HashMap:
publicclassHashMap<K,V> extendsTable/** Whether a value exists under the given key or not */functionhas(Kkey) returnsbooleanreturnhasInt(keycastToint)
/** Saves the given value under the given key */functionput(Kkey, Vvalue)
saveInt(keycastToint, valuecastToint)
/** Retrieves the value saved under the given key */functionget(Kkey) returnsVreturnloadInt(keycastToint) castToV/** Removes the value saved under the given key */functionremove(Kkey)
removeInt(keycastToint)
New HashMap
typeclassIndexable<T>
functiontoIndex(Telem) returnsintfunctionfromIndex(intindex) returnsTpublicclassHashMap<K : Indexable, V : Indexable> extendsTable/** Whether a value exists under the given key or not */functionhas(Kkey) returnsbooleanreturnhasInt(K.toIndex(key))
/** Saves the given value under the given key */functionput(Kkey, Vvalue)
saveInt(K.toIndex(key), V.toIndex(value))
/** Retrieves the value saved under the given key */functionget(Kkey) returnsVreturnV.fromIndex(loadInt(K.toIndex(key)))
/** Removes the value saved under the given key */functionremove(Kkey)
removeInt(K.toIndex(key))
To simplify the transition, we could automatically interpret the old code as the new one.
Also we can automatically create some useful instances for all classes, for example the Indexable above.
A challenge here is that for Jass and Lua different type classes would make sense:
Casting classes to int in Lua is an ugly and leaky workaround and not really needed as we have tables to store stuff without indexes.
4. Disallowing casting between different instantiations
Currently the following code is valid:
classAclassBextendsAfunctionfoo(LinkedList<A> l)
@TestfunctiontestCast()
LinkedList<B> list = asList(newB, newB, newB)
foo(listcastTointcastToLinkedList<A>)
With the proposed changes this would still compile, but might no longer work, since translation might create complete different code for List<A> and List<B>.
Maybe it makes sense to guarantee that all instantiations with class types share the same code (same as it is now), but this is difficult to implement in combination with typeclasses.
Currently, generics are translated using erasure. This means, that generics can only be instantiated with types compatible with
int. The idea of this proposal is to change the translation and instead of using erasure translation would generate one copy per instantiated type. This would allow to extend generics with some interesting new features, but would also be incompatible in some cases.Language changes:
1. Allow all types for generics
Every type can be inserted for a generic type parameter without any restrictions.
For example, we can then use
LinkedList<vec3>.This also allows us to get rid of the implicit calls to
fromIndexandtoIndexthat we currently have as a woraround.2. Allow to restrict type parameters / type classes
Sometimes we want to restrict type parameters.
For example
LinkedList<T>.toString() returns stringonly can be implemented, if we have a methodtoString(T) returns string.We could of course provide this method using an additional argument:
However, it is not very convenient to always pass this
Showvalue explicitly.Instead we allow to use generic interfaces as type constraints:
The compiler automatically finds instances that are defined with the new
instancedefinitions:If multiple instance are in scope there is a compilation error.
The instantiation is part of the type, so the following example is not allowed:
To resolve instances, we first match the parameters and then match the type constraints from left to right.
Type class translation: The translation uses monomorphization (like traits in Rust) when translating to Jass and dictionary passing when translating to Lua.
3. Disallow casting to int
Since we now allow all types to be used for type parameters, we can no longer cast type parameters to
int. However, we can use the new feature of restricted type parameters to implement the same feature:Old
HashMap:New
HashMapTo simplify the transition, we could automatically interpret the old code as the new one.
Also we can automatically create some useful instances for all classes, for example the Indexable above.
A challenge here is that for Jass and Lua different type classes would make sense:
Casting classes to int in Lua is an ugly and leaky workaround and not really needed as we have tables to store stuff without indexes.
4. Disallowing casting between different instantiations
Currently the following code is valid:
With the proposed changes this would still compile, but might no longer work, since translation might create complete different code for
List<A>andList<B>.Maybe it makes sense to guarantee that all instantiations with class types share the same code (same as it is now), but this is difficult to implement in combination with typeclasses.