Generics and assumption of passed in Class type!
Monkey Forums/Monkey Programming/Generics and assumption of passed in Class type!
| ||
Didn't realise monkey allowed this:Function Main:Int() Local list1:= New List<ItemType1> Local list2:= New List<ItemType2> Local item1:= list1.Get(123) Local item2:= list2.Get(456) Print item1.value Print item2.value End Class List<T> Method Get:T(value:Int) Local item:= New T 'generically set field of T without knowing it exists for sure item.value = value Return item End End Class ItemType1 Field a:String = "A" Field value:Int End Class ItemType2 Field b:String = "B" Field value:Int End Quite handy to be able to have a generic function for dealing with unrelated class types... Nice! |
| ||
I wonder if this is just a side effect of type erasure, or if it's related to the (incomplete and supposedly buggy) generic interface stuff Mark left enabled in |
| ||
Looking at the generated output, it looks like everything is compiled correctly. I havn't checked in objc/java outputs but the cpp and javasript builds fine. Teh generics seems to not be using any underlying generics of the target language, but instead hardcoding the individual versions of the generic type with 1 copy per class type used with the generics. e.g. function c_List(){ Object.call(this); } c_List.m_new=function(){ return this; } c_List.prototype.p_Get=function(t_value){ var t_item=c_ItemType1.m_new.call(new c_ItemType1); t_item.m_value=t_value; return t_item; } and function c_List2(){ Object.call(this); } c_List2.m_new=function(){ return this; } c_List2.prototype.p_Get=function(t_value){ var t_item=c_ItemType2.m_new.call(new c_ItemType2); t_item.m_value=t_value; return t_item; } |
| ||
Well, it'll work until it doesn't -- much like assuming a generic is an object type at all. Fine for code only you use but a bad idea in any public library. |
| ||
I don't think its going to be too much of a breaking issue. Monkey seems to catch things nicely: This produces: D:/scratch/jungle/untitled_5.monkey<19> : Error : Identifier 'value' not found. I would agree though that this is probably really only good for internal/privately-scoped code. |
| ||
Basically what happens is that at compile time Monkey will generate a version of the class that matches the generic parameter(s) you've passed in. If you pass in a type that doesn't have that field, the generated code won't compile. This is different to Java, which simply erases all generics at compile time and replaces them with "Object". This is why in Monkey you can instantiate T and arrays of T, because the generated code has actually replaced the generic with its correct type. With type erasure, it would incorrectly try to instantiate Object, so Java refuses to compile it. Edit: A situation like the one you've described is the reason Java's generics have wildcards and bounding. If you know for sure that your generic parameter will extend a certain class (which has the field you're referencing), you can express that in your code like so: public class Foo<T extends Bar> { public void hello(T arg) { // here we know that T will have the value field, because T extends Bar System.out.println(arg.value); } } public class Bar { String value; } public class Test extends Bar { public Test(String value) { this.value = value; } } ... new Foo<Test>.hello(new Test("world")); |
| ||
If you know for sure that your generic parameter will extend a certain class (which has the field you're referencing), you can express that in your code like so how useful is that really, when there isn't much multiple-inheriting? i would think people would default on interfaces getters/setters. |
| ||
how useful is that really, when there isn't much multiple-inheriting? i would think people would default on interfaces getters/setters. Example: class EntityGroup<T extends Entity> { List<T> entities = new ArrayList<>(); public void updateAll() { for(T entity : entities) { entity.update(); // <-- generic bounding makes this possible } } } abstract class Entity { public abstract void update(); } class Enemy extends Entity { public void update() { // update the enemy } } class Bullet extends Entity { public void update() { // update the bullet } } EntityGroup<Enemy> enemies = new EntityGroup<>(); EntityGroup<Bullet> bullets = new EntityGroup<>(); enemies.updateAll(); bullets.updateAll(); |
| ||
Yeah being able to specify the base class that T relates to would be awesome! |
| ||
I get the impression that Mark prefers this "looser" approach to generics over tighter generic constraints (i.e. specifying the base class that T relates to). http://www.monkey-x.com/Community/posts.php?topic=2399&post=23772 |