Delegates
Monkey Forums/Monkey Programming/Delegates
| ||
Anybody else is missing delegates a lot? Without function pointers, this would be a killer feature for me... |
| ||
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] |
| ||
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... |
| ||
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.) |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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? |
| ||
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] |
| ||
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]) |
| ||
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". |
| ||
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] |
| ||
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] |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
Thanks! Cloning now :-) Wow, Control is *huge* |
| ||
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. |
| ||
@ziggy: ...the lack of a "Friend" modifier... But remember, your friends can see your privates. ;) |