Suggestion: generic functions

Monkey Forums/Monkey Programming/Suggestion: generic functions

Gerry Quinn(Posted 2012) [#1]
Hi,

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.


NoOdle(Posted 2012) [#2]
Forgive my potential ignorance, but why can't you use static functions in your generic class?


Gerry Quinn(Posted 2012) [#3]
Unless I'm missing something, Monkey doesn't seem to allow static functions.

That was indeed my original intention.


NoOdle(Posted 2012) [#4]
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



Gerry Quinn(Posted 2012) [#5]
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.


NoOdle(Posted 2012) [#6]
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



Gerry Quinn(Posted 2012) [#7]
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!


NoOdle(Posted 2012) [#8]
No problem, glad you got it solved :)


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


Gerry Quinn(Posted 2012) [#10]
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.


Belimoth(Posted 2013) [#11]
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.


Gerry Quinn(Posted 2013) [#12]
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 )




Belimoth(Posted 2013) [#13]
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


Usage:
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.