Delegates

Monkey Forums/Monkey Programming/Delegates

ziggy(Posted 2012) [#1]
Anybody else is missing delegates a lot? Without function pointers, this would be a killer feature for me...


Samah(Posted 2012) [#2]
Try using MethodInfo.Invoke(). I plan on using it for scripting in the near future. There's already a thread regarding this. I'll give you a link and a code example when I'm not on the bus. :-)

Edit: Here's the thread, and the code sample post:
http://www.monkeycoder.co.nz/Community/posts.php?topic=2645#26659

Edit 2: Copy pasted the Monkey code:
[monkeycode]Class Foo
Method someMethod:Void()
...
End

Method bar:Void()
' somesel is a selector/fp for the "someMethod" method in class Foo
Local somesel:MethodInfo = GetClass(Self).GetMethod("someMethod", [])
' equivalent to calling: Self.someMethod()
somesel.Invoke(Self, [])
End
End[/monkeycode]


ziggy(Posted 2012) [#3]
But you need to keep a pointer to the class and isn't a invoke function a dispatch-like call that's a lot slower than a regular call? Other than that it might do its job...


Samah(Posted 2012) [#4]
What do you mean "a pointer to the class"? Also, reflection in Monkey is very different to most other languages. Invoke is basically just a wrapper that directly calls the relevant method on the object. There's no fancy lookups to be done, and it's all precompiled (unlike Java, etc.)


ziggy(Posted 2012) [#5]
a pointer to the class, sorry... I was refering to a class reference. I didn't know it was a direct call to the class, that's great. Anyway, I'm working on a sort of events system so a reflection based approach can be a valid option as it is not very very imprtant if it is slightly slower than a traditional old-school pointer.


Tibit(Posted 2012) [#6]
I think I'm on the same line as Ziggy. Reflection works as a hack-around, but had downsides. What I want is a simple event system to loosen coupling in code.

I want one class to be able to have "output" events/methods/functions/delegates that can be called within that class. Any number of "external" classes can hook/listen to these "events" and the result will be a direct synchronous invocation (normal invocation).

The only difference from a normal method call is that the event is "hooked" in runtime. This means that no hard depenancy needs to exist between the classes.

This is a simple language-based workaround to the ObserverPattern - without the downside of having to create a lot of extra classes.

Delegates are (the way I see it) events but without the "one to many" aspect. Meaning each delegate (function/method pointer) only calls/points to one function/method.

And with delegates, it would be simple to create fancy, effective and easy to use event systems. And there are also other uses of delegates, though no good example come to mind right now.


ziggy(Posted 2012) [#7]
And there are also other uses of delegates, though no good example come to mind right now.
Very useful on classes design when you want a given class to perform an action depending on its state, you can just modify the address (method pointer) of the given abstracted method. For instance, on lexing designs, it is common to use a delegate to parse "next token" and the address of this method is changed when we're inside a comment, or inside a Extern block, or any other place where code has to be parsed differently, so we do not end with a huge unmanagebla select-case statement, and we can get an easier to maintain class structure that also is much more abstracted and allows additional implementations with easy.


Tibit(Posted 2012) [#8]
Events with Interfaces (Working running example)
[monkeycode]
Function Main:Int()
Local ship:SpaceShip = new SpaceShip
Local fighter:Fighter = new Fighter
ship.Fire = fighter
' Fighter & ship knows about interface OnFireEvent

Print "start"
ship.UpdateControls()
Print "end"
End

Interface OnFireEvent
Method OnFire:Void(weapon:string, power:Int) 'If someone binds this - that method will fire
End

Class SpaceShip

Field Fire:OnFireEvent 'uses the interface

Method UpdateControls:Void()
If True
'Let us fire
Local weapon:String = "Laser"
Local power:Int = 5
Print "Ship: Ready to fire!"
if Fire Then Fire.OnFire(weapon, power)
EndIf
End

End

Class Fighter implements OnFireEvent 'implements interface
Field Energy:Int = 100

Method OnFire:Void(weapon:string, power:Int)
If Energy > power
Print "Fighter: Firing " + weapon + " using " + power + " energy."
EndIf
End
End[/monkeycode]

Example how the same code could look using "events or delegates". (Code not runnable, yet)
[monkeycode]Function Main:Int()
Local ship:SpaceShip = new SpaceShip
Local fighter:Fighter = new Fighter
ship.Fire = fighter.OnEnemyFire
' Fighter knows nothing about ship
' However once the ship.Fire method is called
' then fighter.OnEnemyFire is called
' if the methods does not match, error when we bind

Print "start"
ship.UpdateControls()
Print "end"
End

Class SpaceShip

Event Method Fire:Void(weapon:String:int, power) ' Many other equivalent forms of syntax possible

Method UpdateControls:Void()
If True
'Let us fire
Local weapon:String = "Laser"
Local power:Int = 5
Print "Ship: Ready to fire!"
if Fire Then Fire(weapon, power)
End
End
End

Class Fighter
Field Energy:Int = 100

' Called upon using Event/Delegate
' Name of this method does not matter
Method OnEnemyFire:Void(weapon:string, power:Int)
If Energy > power
Print "Fighter: Firing " + weapon + " using " + power + " energy."
EndIf
End
End[/monkeycode]Is that how you guys see it could work? Any additional differences between interfaces/reflection approach/delegates?


Tibit(Posted 2012) [#9]
EDIT - I got it working using reflection as well:
[monkeycode]Import reflection

Function Main:Int()
Local ship:SpaceShip = new SpaceShip
Local fighter:Fighter = new Fighter

Print "start"

'Hook event
Local onFireEvent:MethodInfo = GetClass("Fighter").GetMethod("OnEnemyFire", [StringClass, IntClass, GetClass("WeaponSlot")], false)

if onFireEvent Then Print "onFireEvent Hooked"
ship.FireMethod = onFireEvent
ship.FireInstance = fighter
' Fighter & ship are unrelated

ship.UpdateControls()
Print "end"
End


Class SpaceShip

Field FireMethod:MethodInfo 'uses reflection
Field FireInstance:Object 'also need this for reflection

Method UpdateControls:Void()
If True
Print "Ship: Ready to fire!"
if FireMethod And FireInstance
'Let us fire
Local weapon:String = "Laser"
Local power:Int = 5
Local weaponSlot:WeaponSlot = new WeaponSlot
weaponSlot.ID = 101 ' Test
Local argumentsArray:Object[] = [BoxString(weapon), BoxInt(power), weaponSlot]

FireMethod.Invoke(FireInstance, argumentsArray) ' Call event
else
Print "event not hooked"
End
EndIf
End

End
Class WeaponSlot
Field ID:Int = 999
End

Class Fighter
Field Energy:Int = 100

Method OnEnemyFire:Void(weapon:string, power:Int, weaponSlot:WeaponSlot = null)
If Energy > power
Print "Fighter: Firing " + weapon + " using " + power + " energy."
if weaponSlot Then Print "using slot " + weaponSlot.ID
EndIf
End
End[/monkeycode]


Tibit(Posted 2012) [#10]
This is my attempt, does run, but does not work as expected - something wrong with the reflection hook.
[monkeycode]
Import reflection

Function Main:Int()
Local ship:SpaceShip = new SpaceShip
Local fighter:Fighter = new Fighter

Print "start"

'Hook event
Local onFireEvent:MethodInfo = GetClass("Fighter").GetMethod("OnEnemyFire", []) 'ERROR
if onFireEvent Then Print "onfireEvent Hooked"
ship.FireMethod = onFireEvent
ship.FireInstance = fighter
' Fighter & ship are unrelated

ship.UpdateControls()
Print "end"
End


Class SpaceShip

Field FireMethod:MethodInfo 'uses reflection
Field FireInstance:Object 'also need this for reflection

Method UpdateControls:Void()
If True
Print "Ship: Ready to fire!"
if FireMethod And FireInstance
'Let us fire
Local weapon:String = "Laser"
Local power:Int = 5
Local argumentsArray:Object[] = [BoxString(weapon), BoxInt(power)]

FireMethod.Invoke(FireInstance, argumentsArray) ' Call event
else
Print "event not hooked"
End
EndIf
End

End

Class Fighter
Field Energy:Int = 100

Method OnEnemyFire:Void(weapon:string, power:Int)
If Energy > power
Print "Fighter: Firing " + weapon + " using " + power + " energy."
EndIf
End
End[/monkeycode]Can you see what it is?

EDIT - I found it:

Error was that
Local onFireEvent:MethodInfo = GetClass("Fighter").GetMethod("OnEnemyFire", []) 'ERROR

Should be:
Local onFireEvent:MethodInfo = GetClass("Fighter").GetMethod("OnEnemyFire", [StringClass, IntClass])


ziggy(Posted 2012) [#11]
I have a complete GUI system that implements an event-poll using reflection and it works wonderfuly. i'll release it soon under an open source license.
Just a small taste of it:

[monkeycode]Import junglegui
Import reflection


Class EventDelegate
Field control:Control
Field signature:Int
Field methodName:String
Field callBack:MethodInfo

Method New(form:TopLevelControl, control:Control, signature:Int, methodName:String)

Local argTypes:=New ClassInfo[2]
argTypes[0] = GetClass(New Control)
argTypes[1] = GetClass(New EventArgs)
Local myclass:=GetClass(form)
callBack = myclass.GetMethod(methodName, argTypes)
self.control = control
Self.signature = signature
Self.methodName = methodName
End

Method Invoke(form:TopLevelControl, sender:Control, e:EventArgs)
Local arguments:Object[] = New Object[2]
arguments[0] = sender
arguments[1] = e
callBack.Invoke(form,arguments)
End

Method Match:Bool(control:Control, signature:Int)
if control = Self.control And signature = Self.signature Then Return True Else Return false
End
End

Class EventHandler
Method New(form:TopLevelControl)
_form = form
End
Method Add:Bool(control:Control, signature:Int, methodName:String)
Local delegate:=New EventDelegate(_form,control,signature,methodName)
if delegate.callBack = null Then Return false
list.AddLast(delegate)
end
Method Remove:Bool(control:Control, signature:Int, methodName:String)
Local remover:EventDelegate = FindSpecific(control,signature,methodName)
if remover<> null Then
list.Remove(remover)
Return true
Else
Return false
EndIf
end
Method FindSpecific:EventDelegate(control:Control, e:EventArgs, methodName:String)
Local result:EventDelegate = null
For Local delegate:EventDelegate = EachIn list
if delegate.control = control And delegate.signature = e.eventSignature And delegate.methodName = methodName
result = delegate
Exit
end
Next
Return result
end

Method FindAll:List<EventDelegate>(control:Control, e:EventArgs)
Local result:=New List<EventDelegate>
For Local delegate:EventDelegate = EachIn list
if delegate.control = control And delegate.signature = e.eventSignature
result.AddLast(delegate)
Exit
end
Next
Return result
end


Method Clear()
list.Clear()
End
Private
field _form:TopLevelControl
Field list:=New List<EventDelegate>
End[/monkeycode]

this is a form class using my gui system. the code is pretty self-explanatory:
[monkeycode]Class MyForm extends Form
'Buttons:
Field butContinue:Button
Field butExit:Button
Field panel2:Panel

'Text fields:
Field managedScreen:GuiScreen
Field txtSubjectNumber:TextField
Field txtSesionNumber:TextField
Field txtTypePD:TextField
Field txtSubjectCode:TextField
Field txtOrderOnOff:TextField
Field txtOnOff:TextField
Field txtSex:TextField
Field txtDurationOfDisease:TextField
Field txtDominantHand:TextField
Field txtExperimenterID:TextField
Field txtSite:TextField

Method New(gui:Gui)
Self.InitForm(gui)
SetFormSize()
CreateComponents()
Gui.systemFont.DrawBorder = False
Gui.systemFont.DrawShadow = false
End

Method SetFormSize()
Position.X = 0
Position.Y = 0
Size.X = DeviceWidth()
Size.Y = DeviceHeight()
End

Method CreateComponents()
'''
''' Self
'''
Self.BackgroundColor = SystemColors.ControlFace.Clone()

'''
''' panel2
'''
panel2 = New Panel()
panel2.Parent = Self
panel2.Position.X = 10
panel2.Position.Y = 10
panel2.Size.X = DeviceWidth - 20
panel2.Size.Y = DeviceHeight - 100
'panel2.BackgroundColor.SetColor(1,255,255,235)

'''
''' mybutton
'''
butContinue=New Button()
butContinue.Parent = self
butContinue.Text = "Next>>>"
butContinue.Position.X = Size.X - butContinue.Size.X - 20
butContinue.Position.Y = Self.Size.Y - butContinue.Size.Y-20
Events.Add(butContinue,eEventKinds.CLICK,"ButContinue_Clicked")

'''
''' But Exit:
'''
butExit=New Button()
butExit.Parent = self
butExit.Text = "Quit"
butExit.Position.X = 20
butExit.Position.Y = butContinue.Position.Y
Events.Add(butExit,eEventKinds.CLICK,"ButExit_Clicked")

'''
''' text fields:
'''
txtSubjectNumber = AddTextField("Subject number",0,"1")
txtSesionNumber= AddTextField("Session number",1,"1")
txtTypePD = AddTextField("Type (PD(1)/CTRL(2))",2,"1")
txtSubjectCode = AddTextField("Subject code",3,"1")
txtOrderOnOff = AddTextField("Order (OnOff(1),OffOn(2))",4,"1")
txtOnOff = AddTextField("OnOff(On(1),Off(2))",5,"1")
txtSex = AddTextField("Sex",6,"1")
txtDurationOfDisease = AddTextField("Duration of disease (1 or 2)",7,"1")
txtDominantHand = AddTextField("DominantHand (L/1, R/2)",8,"1")
txtExperimenterID = AddTextField("Experimenter ID",9,"1")
txtSite = AddTextField("Site",10,"1")

End


Method AddTextField:TextField(labelTxt:String,line:Int,defaultValue:String) 'This is a helper method to fill in the form with text fields.
Const MARGIN:Int = 10
Const CONTROLSIZE:Int = 20

'We create the "prompt" label:
Local label:BaseLabel
label = New BaseLabel(panel2,100,20 + line * (CONTROLSIZE +MARGIN ), labelTxt, 200, CONTROLSIZE )

'We create the TextField we're going to return:
Local txtField := New TextField()
txtField.Parent = panel2
txtField.Position.X = label.Position.X + label.Size.X + MARGIN
txtField.Position.Y = label.Position.Y
txtField.Text = defaultValue
txtField.AutoAdjustSize = False
txtField.Size.X = 200
txtField.Size.Y = label.Size.Y
Return txtField
End

Method ButContinue_Clicked(sender:Control, e:EventArgs)
managedScreen.NextScreen = New InitPage2
managedScreen.SetStatus(eScreenStatus.LOADNEXT ) 'We tell the managedScreen that its status is "Load Next screen".
End
Method ButExit_Clicked(sender:Control, e:EventArgs)
managedScreen.SetStatus(eScreenStatus.EXITBROWSER) 'We tell the managedScreen that its status is END execution.
End
End[/monkeycode]

As you can see in this sample, all you have to do is add this on any Top level control (Form):
[monkeycode]Events.Add(butExit,eEventKinds.CLICK,"ButExit_Clicked")[/monkeycode]
Where the paremters are:
1.- The control instance that will trigger the event we sant to handle
2.- The event kind. It's a constant.
3.- The name of the method we're going to call whever this event is fired. All event-methods have the signature:
[monkeycode]MethodName:Int(sender:Control, e:EventArgs)[/monkeycode]Where "sender" is the control that is firing the event, and "e" is an instance of the event itself. Depending on the event kind, it can contain mouse coordinates, the pressed key, etc etc. It's a lot like windows forms in this respect. It does internally use Reflection and I don't see any way it could work properly without it. Events are executed in a true event-queue, so a single event can be handled by as many "delegates" as desired and they're executed "in cascade".


Tibit(Posted 2012) [#12]
Looks cool.

The example I made, how would it look using your reflection Delegates?

Note: I edited my post about with reflection code and it certainly got a bit messier but also required fewer steps than Interfaces + it also introduced some duck typing (good & bad).

Here is the code in simpler excerpts using raw REFLECTION:

Hook Event[monkeycode]
'Hook event
Local onFireEvent:MethodInfo = GetClass("Fighter").GetMethod("OnEnemyFire", [StringClass, IntClass])
ship.FireMethod = onFireEvent
ship.FireInstance = fighter
[/monkeycode]
Define Event - in the Class that can Output the event[monkeycode]
Field FireMethod:MethodInfo 'uses reflection
Field FireInstance:Object 'also need this for reflection
[/monkeycode]
Invoke Event - requires we can reach definition of event above
[monkeycode]
'Event Parameters:
Local weapon:String = "Laser"
Local power:Int = 5
Local argumentsArray:Object[] = [BoxString(weapon), BoxInt(power)]
FireMethod.Invoke(FireInstance, argumentsArray)
[/monkeycode]


Tibit(Posted 2012) [#13]
In comparison here is the code in simpler excerpts using INTERFACES:

Hook Event[monkeycode]
'Hook event
ship.Fire = fighter
[/monkeycode]
Declare Event - In any imported Module[monkeycode]
Interface OnFireEvent
Method OnFire:Void(weapon:string, power:Int)
End
[/monkeycode]
Allow Class to Hook Event - ForEach class that wants to ACT on the event[monkeycode]
Class Fighter implements OnFireEvent
[/monkeycode]
Define Event - in the Class that will Output/Call the event[monkeycode]
Field Fire:OnFireEvent
[/monkeycode]
Invoke Event - requires we can reach definition of event above[monkeycode]
'Event Parameters:
Local weapon:String = "Laser"
Local power:Int = 5
Fire.OnFire(weapon, power)
[/monkeycode]


ziggy(Posted 2012) [#14]
But how could you dinamically change from one event-handler to another at runtime? Let's say, in a GUI, I want a button tclick to perform action A, and after changing something in the status of the whole app. I want this button to perform B, and later to perform again A and B, etc.. the way I see it, interfaces are much less dynamic in this respect.

My example might look similar. What I have anyway is a system that also uses a sort of windows-message approach, so events are called in a top-down from the control that generates the event, to the top-level container, where it can be handled or just ignored. Other than that, it's quite similar.


muddy_shoes(Posted 2012) [#15]
But how could you dinamically change from one event-handler to another at runtime?


You can just change the referenced interface implementation or you can have logic in the implementation that checks the state and acts accordingly. It's not ideal in all circumstances but I'm not convinced the reflection-based method is much of an improvement.

Not that proper delegates as in C# don't have advantages. I'd be glad to have them. Or functions as objects. In-line declaration would be even better. In fact I'd be happy to get anonymous inner classes or even just inner classes just to be able to declare simple handlers near where they are used and avoid tacking interfaces on classes. And a pony. I want a pony.


ziggy(Posted 2012) [#16]
You can just change the referenced interface implementation
Of course, but that's a bit too much for my taste. I mean, having to implement a new class and class instance for each situation, it's a bit too heavy in my opinion. While the reflection approach is not ideal too, I think it is better in this respect... Anyway, each to their own, I would preffer a 1965 stratocaster than a Pony, but I see your point about the Pony. A pony rocks for sure too, but the Strato sounds a lot better.


TheRedFox(Posted 2012) [#17]
Ziggy, is the event system mentioned above available somewhere? I am currently writing such a thing on my own right now but yours seems to be more fully featured.


ziggy(Posted 2012) [#18]
Yes, it's available on the JungleGui source code.
You can take a look here:
http://code.google.com/p/junglegui/source/browse/eventhandler.monkey


TheRedFox(Posted 2012) [#19]
Thanks! Cloning now :-)

Wow, Control is *huge*


ziggy(Posted 2012) [#20]
Wow, Control is *huge*
Yes, it's big. I wish I could properly spread it on multiple files but the lack of a "Friend" modifier forces me to put the class: Control, ControlContainer, TopLevelControl and Gui in the same monkey module. Not an issue when using Jungle Ide tho.


Samah(Posted 2012) [#21]
@ziggy: ...the lack of a "Friend" modifier...

But remember, your friends can see your privates. ;)