Thread: [F-Script-talk] adding Blocks as methods to ObjC classes
Brought to you by:
pmougin
From: Luke N. <l_n...@ya...> - 2005-02-15 05:45:05
|
Hi, I started work on a new software project and decided that I would include F-Script in it from the ground up. The kind of software I'm building has both a end user development/scripting function as well as a deployment function where efficiency is key. Given F-Script's impressive level of integration with ObjC and Cocoa I decided that I would implement all of the scripting and development capability in F-Script. I thought it would be great to give end developers using this software the flexibility to implement methods in F-Script first for prototyping and rapid deployment, and then later recode those methods that were relatively stable and debugged, and needed the additional speed, into ObjC. I therefore added a few methods to the base class in my class hierarchy to support runtime attachment of F-Script Blocks as methods that can be called transparently, using the standard messaging syntax, from either the ObjC or the F-Script side. For example, the following example script adds the method gimmeFive to an object loaded from the framework I'm developing, which is called the Tenth Art Tools. The TATestingDummy object is an ObjC object with no declared methods or member variables. It inherits all of its functionality from TAObject, the base object in the Tenth Art Tools, of which it is a direct descendant. > TATestingDummy attachFSBlock: [5] toSelector:#gimmefive withArgs:''. tadummy := TATestingDummy alloc init. tadummy gimmefive. 5 Although this example is very simplistic the framework supports up to 11 arguments and inheritance and overriding of methods just like ObjC The created Blocks are callable from the ObjC side as well. If an ObjC method of the same name exists it will always override the F-Script Block method, so you can't override compiled-in methods with this code, you can just add new ones. Please note that so far the technology only allows for the addition of instance methods, not class methods. Adding this functionality has really come in handy in situations where I want to script a method for an object. For example, I am using fscripter to create the testbed for the framework I'm developing. Part of the testbed creates an NSOutlineView to view the test results in an organized fashion. Being able to add methods to this dummy object allowed me to create the delegate object that NSOutlineView uses to display its data and script all of its methods as blocks in F-Script. Although it is not in place yet, the scripted methods will have access to member variables via Cocoa Bindings, mainly Key Value Coding. This means that any object that supports KVC could potentially have attachable methods that are modifiable on the fly and have access to all of the functionality that a regular ObjC method has, yet are changeable during runtime without recompile or even program suspension. As the the Tenth Art Tools are ultimately going to be GPLed or some similar open source model I would be happy to share this code or the design with anyone interested and would welcome feedback on this idea. Did I re-invent the wheel? Is there some handy dandy F-Script way to do this and I just missed it on my read-through of the documentation? Is this code already implemented, say in the JGAdditions? Is there any interest in incorporating this functionality in some way to F-Script itself? If so, it would be an honor. Did I completely not get the point of this language and I'm misusing horribly? I hope not. Feel free to let me know what you think. Thanks, Luke Nihlen |
From: Ken F. <ken...@gm...> - 2005-02-15 08:05:45
|
I've been meaning to post a similar message, so I'll throw in my 2 cents. :-) I have a class that does something similar with a slightly different emphasis. KFDecorator <http://homepage.mac.com/kenferry/Software/KFDecorator/KFDecorator.zip> uses message forwarding + blocks to act like an abstract decorator, with some limitations. I think it's best explained through a couple of examples. I don't use it in release code, but I've found it to be pretty useful when using FScript (Anywhere) as part of the development environment for a cocoa application. It's good for debugging, exploration of ideas, and sometimes automated testing. The idea of a decorator is that it wraps a base object and modifies its behavior. The decorator catches specified messages and processes them, and any messages it doesn't catch are forwarded to the base object. It's a design pattern from the Gang of Four book. > dataDecorator := tableView dataSource decorator > dataDecorator numberOfRowsInTableView:tableView 312 > dataDecorator setBlock:[:self :table | 10] forSelector:#numberOfRowsInTableView: > dataDecorator setBlock:[:self :table :column :row | 'test'] forSelector:#tableView:objectValueForTableColumn:row: > dataDecorator <KFDecorator[0x4cf5b60] base:<TableViewManager: 0x4c67a0> maps:{ :#numberOfRowsInTableView: => [:self :table | 10] #tableView:objectValueForTableColumn:row: => [:self :table :column :row | 'test'] }> > dataDecorator numberOfRowsInTableView:tableView 10 > dataDecorator tableView:tableView objectValueForTableColumn:nil row:0 'test' > tableView setDataSource:dataDecorator The main interesting thing about the above example is that it shows the decorator translating argument and return types. The 'numberOfRowsInTableView:' method returns a genuine int, which makes the decorator more suitable for interacting with objective-c code that doesn't use objects. (Sounds like you did this too, Luke.) The class was written before the latest version of FScript came out, so it uses the deprecated Pointer class to do the dirty work. The argument and return types are determined from the method signature of the overridden base method, when there is such a base method. You can use a decorator without a base, in which case it acts more like a dynamically built object. > decorator := KFDecorator decorator > decorator setBlock:[:self :num | num + 4] forSelector:#addFourToNumber: > decorator <KFDecorator[0x4ca8c0] base:(null) maps:{ #addFourToNumber: => [:self :num | num + 4] }> > decorator addFourToNumber:5 9 When adding a block for a method not implemented by the base, all arguments and returns are assumed to be objects. There is a -[KFDecorator setBlock:forSelector:withSignature:] method for when that assumption isn't going to hold. You can also use KFDecorator to switch between existing objective-c methods that you'd like to evaluate. > decorator setBlock:#doStuff_alternateAlgorithm forSelector:#doStuff Where in a normal subclassing situation you would call 'super', with a decorator you can call 'self base'. The following would help you understand under what circumstances the numberOfRowsInTableView: method is called. > dataDecorator setBlock:[:self :table | sys beep. self base numberOfRowsInTableView:table] forSelector:#numberOfRowsInTableView: The two biggest limitations are: (1) You cannot set a block for a method implemented by KFDecorator itself, and KFDecorator is a subclass of NSObject as opposed to NSProxy. This is because I haven't worked around NSProxy's lack of a useful -[NSProxy methodSignatureForSelector:]. (2) Since it's just a wrapper for a base object, you can only catch messages when you interject the decorator between a calling object and the base. That can be a good thing or a bad thing in different situations. Okay, so now that that's out, here are some thoughts. :-) I do think that JGType steals some (most?) of the thunder here, but I haven't had much luck getting JGType to work. My app crashes whenever I try to use the dynamic subclassing. Can we get a little bit of sample code on that? You've probably noticed that the first argument of every block set for a selector is :self, which is a reference to the decorator object. In the first version of KFDecorator I had it set up so that all of the following worked: > decorator setBlock:[4] forSelector:#count > decorator setBlock:[ :self | 4] forSelector:#count > decorator setBlock:[ :self :cmd | 4 ] forSelector:#count I couldn't decide if it was confusing and error prone or useful, but eventually changed it to always require the second form. Just another thing to think about. Luke, I'd definitely be interested to see your version of things. We should exchange ideas. :-) You can download my code from the link above. -Ken |
From: Philippe M. <pm...@ac...> - 2005-02-20 17:49:24
|
Le 15 f=E9vr. 05, =E0 09:05, Ken Ferry a =E9crit : > I do think that JGType steals some (most?) of the thunder here, but I > haven't had much luck getting JGType to work. My app crashes whenever > I try to use the dynamic subclassing. Can we get a little bit of > sample code on that? Hi Ken, Here is a basic example where we create a new subclass of NSObject,=20 named MyClass, with three instance variables named a, b and c, and=20 define a simple method named method1: > NSObject subclass:'MyClass' instanceVariableNames:'a b c' MyClass > MyClass MyClass > MyClass setFSBlock:[:self :_cmd| 'Method ' ++ _cmd ++ ' called on '=20= ++ self printString] asInstanceMethod:#method1 > myInstance :=3D MyClass alloc init > myInstance method1 'Method method1 called on <MyClass: 0x4bb69c0>' Best, Philippe Mougin= |
From: Luke N. <l_n...@ya...> - 2005-02-16 08:54:48
|
Ken, Thanks for the detailed response! I'd like to peruse your code, unfortunately I don't have permissions to access the zip file at the link you provided. I also hadn't looked at any of the JGAddon stuff in the sources. A quick glance shows that their work is far more aggressive than what I had attempted or even envisioned for my current project. However, it would be an impressive addition to F-Script to be able to dynamically add objects to the ObjC runtime system. I'm new to ObjC and even newer to F-Script, and I haven't read your code so please forgive any wrong assumptions, but it sounds like our designs seem somewhat similar, at least from the outside. With regard to type, I am actually hoping to do some intelligent return conversion between F-Script and ObjC. Recall that the prototype for the method to attach blocks as methods is: TATestingDummy attachFSBlock: [5] toSelector:#gimmefive withArgs:'' The withArgs argument is a string encoding the types of the return, two @@s for self and cmd, and whatever characters are needed to describe the desired argument types. This encoding is taken from the F-Script source, and is basically what the @encode() compiler directive would return for a function call of your method. I figured out how to do make my own encoding strings on this apple web page: http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/ RuntimeOverview/chapter_4_section_6.html As the code is still in prototype form I haven't had time to fully explore what is needed to perform return type conversion. Fortunately, I seem to be relatively protected on the calling side, as these attached Block methods appear like regular ObjC method to F-Script so it is nice enough to automatically type convert the arguments to ObjC friendly types. Blocks always return id type, however, so methods interested in non-id return types will have to undergo conversion, which is informed by the withArgs: string provided at attachment time. I am also restructuring this encoding stuff to allow self to be handed in so that I can use Key Value Coding to set member variables in self, as all objects in my framework support KVC as I think cocoa bindings are almost as mind blowing as F-Script. Almost. :) I have responded to a couple of things in your email below: On Feb 15, 2005, at 1:05 AM, Ken Ferry wrote: > I've been meaning to post a similar message, so I'll throw in my 2 > cents. :-) > > I have a class that does something similar with a slightly different > emphasis. KFDecorator > <http://homepage.mac.com/kenferry/Software/KFDecorator/KFDecorator.zip> > uses message forwarding + blocks to act like an abstract decorator, > with some limitations. I think it's best explained through a couple > of examples. I don't use it in release code, but I've found it to be > pretty useful when using FScript (Anywhere) as part of the development > environment for a cocoa application. It's good for debugging, > exploration of ideas, and sometimes automated testing. > > The idea of a decorator is that it wraps a base object and modifies > its behavior. The decorator catches specified messages and processes > them, and any messages it doesn't catch are forwarded to the base > object. It's a design pattern from the Gang of Four book. > >> dataDecorator := tableView dataSource decorator > >> dataDecorator numberOfRowsInTableView:tableView > 312 > >> dataDecorator setBlock:[:self :table | 10] >> forSelector:#numberOfRowsInTableView: > >> dataDecorator setBlock:[:self :table :column :row | 'test'] >> forSelector:#tableView:objectValueForTableColumn:row: > >> dataDecorator > <KFDecorator[0x4cf5b60] base:<TableViewManager: 0x4c67a0> maps:{ > :#numberOfRowsInTableView: => [:self :table | 10] > #tableView:objectValueForTableColumn:row: => [:self :table > :column :row | 'test'] > }> Here's maybe a difference - it looks like you are adding methods to instances? I am adding methods to classes. My attachFSBlock:blahblah method is a class method. >> dataDecorator numberOfRowsInTableView:tableView > 10 > >> dataDecorator tableView:tableView objectValueForTableColumn:nil row:0 > 'test' > >> tableView setDataSource:dataDecorator > > The main interesting thing about the above example is that it shows > the decorator translating argument and return types. The > 'numberOfRowsInTableView:' method returns a genuine int, which makes > the decorator more suitable for interacting with objective-c code that > doesn't use objects. (Sounds like you did this too, Luke.) The class > was written before the latest version of FScript came out, so it uses > the deprecated Pointer class to do the dirty work. The argument and > return types are determined from the method signature of the > overridden base method, when there is such a base method. > Since my methods are actually ObjC methods on a class I am hoping F-Script wraps arguments for me. I have interest in writing type conversion for the return, but haven't needed to yet since id is a pretty useful return type for most purposes. > You can use a decorator without a base, in which case it acts more > like a dynamically built object. > >> decorator := KFDecorator decorator > >> decorator setBlock:[:self :num | num + 4] >> forSelector:#addFourToNumber: > >> decorator > <KFDecorator[0x4ca8c0] base:(null) maps:{ > #addFourToNumber: => [:self :num | num + 4] > }> > >> decorator addFourToNumber:5 > 9 > When adding a block for a method not implemented by the base, all > arguments and returns are assumed to be objects. There is a > -[KFDecorator setBlock:forSelector:withSignature:] method for when > that assumption isn't going to hold. > ah, is that an NSMethodSignature? > You can also use KFDecorator to switch between existing objective-c > methods that you'd like to evaluate. > >> decorator setBlock:#doStuff_alternateAlgorithm forSelector:#doStuff > Where in a normal subclassing situation you would call 'super', with a > decorator you can call 'self base'. The following would help you > understand under what circumstances the numberOfRowsInTableView: > method is called. > >> dataDecorator setBlock:[:self :table | sys beep. self base >> numberOfRowsInTableView:table] forSelector:#numberOfRowsInTableView: > The two biggest limitations are: > > (1) You cannot set a block for a method implemented by KFDecorator > itself, and KFDecorator is a subclass of NSObject as opposed to > NSProxy. This is because I haven't worked around NSProxy's lack of a > useful -[NSProxy methodSignatureForSelector:]. > I wrote a working version of -methodSignatureForSelector by looking at the F-Script code, maybe you can model one for NSProxy off of what I wrote. > (2) Since it's just a wrapper for a base object, you can only catch > messages when you interject the decorator between a calling object and > the base. That can be a good thing or a bad thing in different > situations. > are you using forwardInvocation? Why do you have this limitation? > Okay, so now that that's out, here are some thoughts. :-) > > I do think that JGType steals some (most?) of the thunder here, but I > haven't had much luck getting JGType to work. My app crashes whenever > I try to use the dynamic subclassing. Can we get a little bit of > sample code on that? > You've probably noticed that the first argument of every block set for > a selector is :self, which is a reference to the decorator object. In > the first version of KFDecorator I had it set up so that all of the > following worked: > >> decorator setBlock:[4] forSelector:#count > >> decorator setBlock:[ :self | 4] forSelector:#count > >> decorator setBlock:[ :self :cmd | 4 ] forSelector:#count > right I have decided on the second one as well. > I couldn't decide if it was confusing and error prone or useful, but > eventually changed it to always require the second form. Just another > thing to think about. > > Luke, I'd definitely be interested to see your version of things. We > should exchange ideas. :-) You can download my code from the link > above. > I am grooming my code to share, plus should have a usable server up in a few days (just moved) so I'll let you know when there's a zip available for DL. > -Ken > thanks again for replying! Luke > > ------------------------------------------------------- > SF email is sponsored by - The IT Product Guide > Read honest & candid reviews on hundreds of IT Products from real > users. > Discover which products truly live up to the hype. Start reading now. > http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click > _______________________________________________ > F-Script-talk mailing list > F-S...@li... > https://lists.sourceforge.net/lists/listinfo/f-script-talk > |
From: Ken F. <ken...@gm...> - 2005-02-16 10:53:54
|
On Wed, 16 Feb 2005 01:54:42 -0700, Luke Nihlen <l_n...@ya...> wrote: > Ken, > > Thanks for the detailed response! I'd like to peruse your code, > unfortunately I don't have permissions to access the zip file at the > link you provided. Huh. As far as I can tell the link (and permissions) are fine, so maybe try it again? http://homepage.mac.com/kenferry/Software/KFDecorator/KFDecorator.zip > Since my methods are actually ObjC methods on a class I am hoping > F-Script wraps arguments for me. I have interest in writing type > conversion for the return, but haven't needed to yet since id is a > pretty useful return type for most purposes. The way I did it I needed to explicitly convert plain c arguments to objects, but you may have tapped into FScript's conversion stuff more directly than I did. > > When adding a block for a method not implemented by the base, all > > arguments and returns are assumed to be objects. There is a > > -[KFDecorator setBlock:forSelector:withSignature:] method for when > > that assumption isn't going to hold. > > > ah, is that an NSMethodSignature? Yep. They're a pain to create with documented methods, but there's an undocumented +[NSMethodSignature signatureWithObjCTypes:] that makes it pretty straightforward. The argument is a string of the sort you were talking about (e.g. "i@:@" means returns int, takes id, SEL, id as arguments). Your tack of taking a types string directly is a good idea. I'll change that. > > (1) You cannot set a block for a method implemented by KFDecorator > > itself, and KFDecorator is a subclass of NSObject as opposed to > > NSProxy. This is because I haven't worked around NSProxy's lack of a > > useful -[NSProxy methodSignatureForSelector:]. > > > > I wrote a working version of -methodSignatureForSelector by looking at > the F-Script code, maybe you can model one for NSProxy off of what I > wrote. I'll check it out. :-) > > (2) Since it's just a wrapper for a base object, you can only catch > > messages when you interject the decorator between a calling object and > > the base. That can be a good thing or a bad thing in different > > situations. > > > are you using forwardInvocation? Why do you have this limitation? Because I'm using forwardInvocation in the decorator (wrapper) object. If the base object sends a message to itself then the wrapper never sees it. > I am grooming my code to share, plus should have a usable server up in > a few days (just moved) so I'll let you know when there's a zip > available for DL. Looking forward to it. Cheers, Ken |
From: Philippe M. <pm...@ac...> - 2005-02-20 17:19:03
|
Le 15 f=E9vr. 05, =E0 06:45, Luke Nihlen a =E9crit : > [...] > As the the Tenth Art Tools are ultimately going to be GPLed or some=20 > similar open source model I would be happy to share this code or the=20= > design with anyone interested and would welcome feedback on this idea.=20= > Did I re-invent the wheel? Is there some handy dandy F-Script way to=20= > do this and I just missed it on my read-through of the documentation? =20= > Is this code already implemented, say in the JGAdditions? Is there any=20= > interest in incorporating this functionality in some way to F-Script=20= > itself? If so, it would be an honor. Did I completely not get the=20 > point of this language and I'm misusing horribly? I hope not. Feel=20= > free to let me know what you think. I Luke, Your message, as well as Ken's ones, come at a good time since I'm in=20 the process of trying to understand how to add this kind of features to=20= F-Script. In the latest release I have incorporated JGType, from J=F6rg=20= Garbers, which provides related functionalities and is a good way to=20 experiment this kind of extension of F-Script. So feel free to further=20= share your experience with us. If you have code to share with others, I=20= would be happy to host it on the F-Script web site, or to link to it.=20 Also, feel free to tell us more about the project you are working on. I=20= think we are all interested to know how F-Script is used. =46rom my experience with JGType I already see some of the things that=20= I'll have to implement in the core F-Script engine to offer good=20 support for class creation in F-Script. For example, I plan to add the=20= support for sending messages to super. I'm also looking at ways to=20 offer access instance variables directly from F-Script code (without=20 relying on KVC) etc. Best, Philippe Mougin= |