Suggestion: generic functionsMonkey Programming Forums/Monkey Programming/Suggestion: generic functions
This post grew out of a discussion called 'Randomising an Array'. I was interested in making a generic shuffling function that would shuffle (say) an array of Card objects.
My first thought was that an object array is surely just an array of pointers in reality, so it should be possible to just downcast them to object pointers and shuffle any array of objects without generics at all. Unfortunately that doesn't seem to be possible (why?).
I found I could do it with a generic class
Class ArrayRandomiser< T > Method New( _arr:T, nToSwap:Int = -1 ) ' code removed for brevity End End
Then to use it I just write:
New ArrayRandomiser< Card >( myCardArray )
However I thought it would be nice to have proper generic functions without the extraneous 'New'. I thought to make a class called Generic which would hold functions such as ArrayRandomiser. It looks like:
Class Generic< T > Method RandomiseArray:Void( _arr:T, nToSwap:Int = -1 ) ' code removed End ' more methods... End
This works too but the syntax for using it is pretty horrid - I have to write:
New Generic< Card >().RandomiseArray( myCardArray )
Anyway, it occurred to me that generic functions could be added to Monkey very easily by just replicating behind the scenes what I did here. Suppose you wrote code like:
Function< T > RandomiseArray:Void( _arr:T )
It would be converted into a method of a hidden class like my Generic< T >. Then when you call the function, like:
RandomiseArray( myCardArray )
..it would be converted into:
New Generic< Card >().RandomiseArray( myCardArray )
...and compiled as normal. This conversion should be simple because the type of myCardArray is known at compile time.
Actually it occurs to me that maybe it is not quite so simple if there are a lot of methods with the same name and different signatures. So there could be an option to specify it with:
RandomiseArray<Card>( myCardArray )
Anyway, just a suggestion. For now I'll carry on with the class method which works fine! It just seems like there should be a more elegant way than instantiating classes that don't really need to exist.
| Forgive my potential ignorance, but why can't you use static functions in your generic class? |
| Unless I'm missing something, Monkey doesn't seem to allow static functions.|
That was indeed my original intention.
| I think it does... I've used them before!|
Class Foo Function StaticFunction() Print "wooo" End Function End Class Function Main() Foo.StaticFunction() End Function
| You're right - and now that I think of it so have I, ot static fields anyway. But it doesn't seem to work with my Generic< T > class. I get a compile error saying "method cannot be accessed from here".|
Maybe generic classes cannot have static functions.
| this appears to work:|
Class Generic< T > '/ Static Function Function RandomiseArray:Void( _arr:T, nToSwap:Int = -1 ) End Function End Class Function Main() Local this : Float = [ 0.0, 1.0, 2.0 ] Generic< Float >.RandomiseArray( this ) End Function
| LOL - I spent an age testing it and suddenly realised... you are using Function instead of method! That works perfectly!|
So, it looks like my problem is solved: I can have generic functions with the simple syntax as above. Thanks!
| No problem, glad you got it solved :) |
| Err, the code I posted in the randomise array thread was exactly this - a generic class with a function. That was kind of the whole point as you were asking for a way to create a generic function. |
| Yes, I seem to have developed a slight blindness to the concept of class functions as distinct from methods. I kind of forgot about them and thought that Monkey didn't do static methods. |
| Old thread, but this would be a very useful feature.|
Class Entity Field components := New StringMap<Component> Method GetComponent<T>:T() Local name:String = GetClass( New T() ).Name Return T( components.Get(name) ) End Method AddComponent:Void(component:Component) Local key:String = GetClass(component).Name components.Set(key, component) End Method AddComponent<T>:Void() Local component := New T() AddComponent(component) End End
Much more elegant than anything I'm doing now.
| Won't that work if you make it class Entity< T >?|
I use a class called Generic to make generic functions, for example:
Class Generic< T > ' Allocate a 1D array of objects Function AllocateArray:T( i:Int ) Local arr:T = New T[ i ] Return arr End End ' Usage Local myArray:Point = Generic< Point >.AllocateArray( 10 )
| That would work if there was only one class of component.|
You could use an intermediate class ComponentGetter<T> but then the GetComponent method would have to be outside of the Entity class.
Class Entity Field components := New StringMap<Component> Method AddComponent:Void(component:Component) Local key:String = GetClass(component).Name components.Set(key, component) End End Class ComponentGetter<T> Global name:String = GetClass( New T() ).Name Function Get:T(entity:Entity) Return T( entity.components.Get(name) ) End End
mycomponent := ComponentGetter<MyComponent>().Get(myEntity)
Messy and obtuse, might as well just use a cast at that point.
myComponent := MyComponent( myEntity.GetComponent("MyComponent") )
EDIT: A better solution could also be had if some allowances were made for cyclic declaration of classes:
Class Entity Field components := New StringMap<Component> Method AddComponent:Void(component:Component) Local key:String = GetClass(component).Name components.Set(key, component) End End Class Component End Class ComponentBase<T> Extends Component Global name:String = GetClass( New T() ).Name Function GetFrom:T(entity:Entity) Return T( entity.components.Get(name) ) End End Class MyComponent Extends ComponentBase<MyComponent> Method Foo:Void() Print "Bar" End End Function Main:Int() Local myEntity := New Entity() myEntity.AddComponent( New MyComponent() ) Local myComponent := MyComponent.GetFrom(entity) myComponent.Foo() Return 0 End
My considerations here are the ease of creating new Component classes, and the ease of adding/accessing components to/from entities.
EDIT AGAIN: As a sidenote, I had problems with having name as a Global variable. I suspect generics are to blame for this but I am not sure.