Generics and assumption of passed in Class type!

Monkey Forums/Monkey Programming/Generics and assumption of passed in Class type!

Skn3(Posted 2014) [#1]
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!


Nobuyuki(Posted 2014) [#2]
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 newersome versions of Monkey. In any case, be careful with this, because my guess is that if the member doesn't exist, it's probably going to be caught by the target compiler -- trans will give you no guarantees of output sanity.


Skn3(Posted 2014) [#3]
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;
}



muddy_shoes(Posted 2014) [#4]
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.


Skn3(Posted 2014) [#5]
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.


Samah(Posted 2014) [#6]
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"));



AdamRedwoods(Posted 2014) [#7]
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.


Samah(Posted 2014) [#8]
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();



Skn3(Posted 2014) [#9]
Yeah being able to specify the base class that T relates to would be awesome!


Arjailer(Posted 2014) [#10]
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