[Pharo-project] [NB] Speaking about callbacks
Hello, there i am happy to say that during last week, i found a cure for my callback code, and so finally callbacks are now working without crashing the VM. :) But before releasing it to public, first, i need to update VMs for that, because of additional callback support code in VM. Right now, i'd like to discuss about different ways of defining/creating callbacks. And i need more input from people, because i think the way how i implemented it is good for get started, but it looks like there could be more convenient forms :) So, the existing implementation, for creating a callback, you must make a subclass of NBFFICallback, overide #fnSpec method and then instantiate it by using: callback := MyCallBack on: [:... some block ] and then you must pass it somewhere (to some external function), and so, when callback get called, your block will handle it. In objective-C, however it would be much nicer to have a smalltalk objects act as a Obj-C objects (so implementing a methods for it will naturally map them as a callbacks ). For instance imagine that i wanna make own subclass of NSFoo obj-C class and wanna override the #bar method. And then i passing it as an obj-c instance around and each time ,Obj-C #bar message is sent to it , i got a #bar message sent to my smalltalk instance. That would be really cool, isnt? Despite it is only applicable to obj-c because of similar object model :) I imagine it could be done as following: in #bar method you putting a special pragma: MyClass>>bar <objCMethod: 'int ()'> ^ 5 that's it, you telling the compiler that given method can be called from Objective-C.. and you don't have to manually instantiate callbacks etc.. In fact, same form can be used for non-objC callbacks , you must just tell something like: callback := (MyClass>>#bar) asCallbackFor: myObject. here, you instantiating a callback, which when called, the object 'myObject' will receive #bar message. But unlike from Obj-C this callback won't work for all instances of MyClass, but just for specific one. And third form is a variation of original one, instead of subclassing the NBFFICallback, actually i can change implementation to use special factory class(es) for it, so to create a callback you will do something like: callbackFactory := NBFFICallback withSignature: #( int (int x, void * bar)). callback := callbackFactory on: [:x :bar | .... ]. So, i'd like to hear your input, which one you like, and which ones you prefer to have 1. requires creating a subclass of NBFFICallback, overriding it's fnSpec method. Takes a block closure as a callback. 2. first you must create a factory object by specifying a callback signature, then you can instantiate new callbacks by passing a block closure to that factory 3. to create a callback you must specify it's signature in one of the compiled methods, and specify an object which will receive a message when callback will be called. -- Best regards, Igor Stasenko.
On Sep 16, 2012, at 7:17 PM, Igor Stasenko wrote:
Hello, there
i am happy to say that during last week, i found a cure for my callback code, and so finally callbacks are now working without crashing the VM. :) But before releasing it to public, first, i need to update VMs for that, because of additional callback support code in VM.
Right now, i'd like to discuss about different ways of defining/creating callbacks. And i need more input from people, because i think the way how i implemented it is good for get started, but it looks like there could be more convenient forms :)
So, the existing implementation, for creating a callback, you must make a subclass of NBFFICallback, overide #fnSpec method and then instantiate it by using:
callback := MyCallBack on: [:... some block ]
and then you must pass it somewhere (to some external function), and so, when callback get called, your block will handle it.
In objective-C, however it would be much nicer to have a smalltalk objects act as a Obj-C objects (so implementing a methods for it will naturally map them as a callbacks ).
For instance imagine that i wanna make own subclass of NSFoo obj-C class and wanna override the #bar method. And then i passing it as an obj-c instance around and each time ,Obj-C #bar message is sent to it , i got a #bar message sent to my smalltalk instance. That would be really cool, isnt? Despite it is only applicable to obj-c because of similar object model :)
I imagine it could be done as following: in #bar method you putting a special pragma:
MyClass>>bar <objCMethod: 'int ()'> ^ 5
that's it, you telling the compiler that given method can be called from Objective-C.. and you don't have to manually instantiate callbacks etc..
In fact, same form can be used for non-objC callbacks , you must just tell something like:
callback := (MyClass>>#bar) asCallbackFor: myObject.
here, you instantiating a callback, which when called, the object 'myObject' will receive #bar message. But unlike from Obj-C this callback won't work for all instances of MyClass, but just for specific one.
And third form is a variation of original one, instead of subclassing the NBFFICallback, actually i can change implementation to use special factory class(es) for it, so to create a callback you will do something like:
callbackFactory := NBFFICallback withSignature: #( int (int x, void * bar)).
callback := callbackFactory on: [:x :bar | .... ].
So, i'd like to hear your input, which one you like, and which ones you prefer to have
1. requires creating a subclass of NBFFICallback, overriding it's fnSpec method. Takes a block closure as a callback. 2. first you must create a factory object by specifying a callback signature, then you can instantiate new callbacks by passing a block closure to that factory 3. to create a callback you must specify it's signature in one of the compiled methods, and specify an object which will receive a message when callback will be called.
for what it is worth I like the 3rd form. Now I'm not sure on: is the right selector.
On 16.09.2012 20:42, Stéphane Ducasse wrote:
On Sep 16, 2012, at 7:17 PM, Igor Stasenko wrote:
So, i'd like to hear your input, which one you like, and which ones you prefer to have
1. requires creating a subclass of NBFFICallback, overriding it's fnSpec method. Takes a block closure as a callback. 2. first you must create a factory object by specifying a callback signature, then you can instantiate new callbacks by passing a block closure to that factory 3. to create a callback you must specify it's signature in one of the compiled methods, and specify an object which will receive a message when callback will be called. for what it is worth I like the 3rd form. Now I'm not sure on: is the right selector.
+1, out of those listed #3 seems the most convenient to use. Being able to do something like: Integer >> #+ <types: #( int (int aNumber) )> Then specify the callback parameter as self callback: 3 with: #+ would be really neat. As an added bonus, it also highlights the problems this approach would have with simply annotating normal methods which utilize multiple return/parameter types :) Cheers, Henry
On 17 September 2012 11:19, Henrik Sperre Johansen <henrik.s.johansen@veloxit.no> wrote:
On 16.09.2012 20:42, Stéphane Ducasse wrote:
On Sep 16, 2012, at 7:17 PM, Igor Stasenko wrote:
So, i'd like to hear your input, which one you like, and which ones you prefer to have
1. requires creating a subclass of NBFFICallback, overriding it's fnSpec method. Takes a block closure as a callback. 2. first you must create a factory object by specifying a callback signature, then you can instantiate new callbacks by passing a block closure to that factory 3. to create a callback you must specify it's signature in one of the compiled methods, and specify an object which will receive a message when callback will be called.
for what it is worth I like the 3rd form. Now I'm not sure on: is the right selector.
+1, out of those listed #3 seems the most convenient to use.
Being able to do something like: Integer >> #+ <types: #( int (int aNumber) )>
Then specify the callback parameter as self callback: 3 with: #+
would be really neat.
As an added bonus, it also highlights the problems this approach would have with simply annotating normal methods which utilize multiple return/parameter types :)
not sure i understood your sentence about problems.
Cheers, Henry
-- Best regards, Igor Stasenko.
On 17.09.2012 11:48, Igor Stasenko wrote:
On 17 September 2012 11:19, Henrik Sperre Johansen <henrik.s.johansen@veloxit.no> wrote:
On 16.09.2012 20:42, Stéphane Ducasse wrote:
On Sep 16, 2012, at 7:17 PM, Igor Stasenko wrote:
So, i'd like to hear your input, which one you like, and which ones you prefer to have
1. requires creating a subclass of NBFFICallback, overriding it's fnSpec method. Takes a block closure as a callback. 2. first you must create a factory object by specifying a callback signature, then you can instantiate new callbacks by passing a block closure to that factory 3. to create a callback you must specify it's signature in one of the compiled methods, and specify an object which will receive a message when callback will be called. for what it is worth I like the 3rd form. Now I'm not sure on: is the right selector.
+1, out of those listed #3 seems the most convenient to use.
Being able to do something like: Integer >> #+ <types: #( int (int aNumber) )>
Then specify the callback parameter as self callback: 3 with: #+
would be really neat.
As an added bonus, it also highlights the problems this approach would have with simply annotating normal methods which utilize multiple return/parameter types :)
not sure i understood your sentence about problems.
Integer + can return LargePositiveIntegers as well as SmallIntegers. Argument type can be anything from float, to Fraction, to.. well, any kind of number. So annotating such methods with a single <types> annotation (which is to serve all uses of it as a callback), rather than defining signature on a per-use basis can be somewhat limiting and end up working against the inherent polymorphism you (at least when it comes to numerics) see in usual smalltalk code. When it comes to return values that's mostly fine, you simply raise an error if unable to convert to the specified type from what the smalltalk method actually returns. But for parameters, you'd have to define separate methods per signature, say addFloat: <types: #( int (float aNumber) )> addUnsignedInt: <types: #( int (uint aNumber) )> when #+ actually handles those cases correctly. Maybe a way to "override" the default parameter signatures when specifying the callback would be nice? self callback: 3 with: #+ parameterTypes: {aNumber -> float} Leaves the usefulness of a default signature for a method, but enables you to change it on a per-use basis, rather than pollute the base-image with extra methods covering parametric variances. Cheers, Henry
On 17 September 2012 12:06, Henrik Sperre Johansen <henrik.s.johansen@veloxit.no> wrote:
On 17.09.2012 11:48, Igor Stasenko wrote:
On 17 September 2012 11:19, Henrik Sperre Johansen <henrik.s.johansen@veloxit.no> wrote:
On 16.09.2012 20:42, Stéphane Ducasse wrote:
On Sep 16, 2012, at 7:17 PM, Igor Stasenko wrote:
So, i'd like to hear your input, which one you like, and which ones you prefer to have
1. requires creating a subclass of NBFFICallback, overriding it's fnSpec method. Takes a block closure as a callback. 2. first you must create a factory object by specifying a callback signature, then you can instantiate new callbacks by passing a block closure to that factory 3. to create a callback you must specify it's signature in one of the compiled methods, and specify an object which will receive a message when callback will be called.
for what it is worth I like the 3rd form. Now I'm not sure on: is the right selector.
+1, out of those listed #3 seems the most convenient to use.
Being able to do something like: Integer >> #+ <types: #( int (int aNumber) )>
Then specify the callback parameter as self callback: 3 with: #+
would be really neat.
As an added bonus, it also highlights the problems this approach would have with simply annotating normal methods which utilize multiple return/parameter types :)
not sure i understood your sentence about problems.
Integer + can return LargePositiveIntegers as well as SmallIntegers. Argument type can be anything from float, to Fraction, to.. well, any kind of number.
So annotating such methods with a single <types> annotation (which is to serve all uses of it as a callback), rather than defining signature on a per-use basis can be somewhat limiting and end up working against the inherent polymorphism you (at least when it comes to numerics) see in usual smalltalk code.
When it comes to return values that's mostly fine, you simply raise an error if unable to convert to the specified type from what the smalltalk method actually returns.
But for parameters, you'd have to define separate methods per signature, say addFloat: <types: #( int (float aNumber) )> addUnsignedInt: <types: #( int (uint aNumber) )>
when #+ actually handles those cases correctly.
Maybe a way to "override" the default parameter signatures when specifying the callback would be nice? self callback: 3 with: #+ parameterTypes: {aNumber -> float}
Leaves the usefulness of a default signature for a method, but enables you to change it on a per-use basis, rather than pollute the base-image with extra methods covering parametric variances.
Ah, yeah.. i get it. Thought the same way but asked for clarification. This is not a big deal actually, since i will introduce a 'OO-callback' which performs a message send to an object using certain selector, it will be possible, of course, to pass the signature using other way, i.e. as an extra argument: callback := 3 createCallbackFor: #+ signature: #( int (int arg) ) as well as: callback := 3 createCallbackFor: #+ the only difference between those two that upon creating a callback, in first you put signature explicitly, while in another one, NB will do a method lookup and then extract the signature from that method (or raise an error, if signature cannot be found).
Cheers, Henry
-- Best regards, Igor Stasenko.
On 17.09.2012 12:28, Igor Stasenko wrote:
This is not a big deal actually, since i will introduce a 'OO-callback' which performs a message send to an object using certain selector, it will be possible, of course, to pass the signature using other way, i.e. as an extra argument:
callback := 3 createCallbackFor: #+ signature: #( int (int arg) )
as well as:
callback := 3 createCallbackFor: #+ My 2c: Does the behavior differ based on the objects class / dynamic state? (In other words, are there multiple implementors? Does it use private instvars to accomplish it?) If not, then I think 'OO-style' is a less elegant solution than keeping that functionality/responsibility in a class more intricately linked to what it is used for.
The march towards the omniprescent Object which knows all, sees all and does all is tangible, and, at least in my eyes, undesirable :) Cheers, Henry
On 17 September 2012 13:24, Henrik Sperre Johansen <henrik.s.johansen@veloxit.no> wrote:
On 17.09.2012 12:28, Igor Stasenko wrote:
This is not a big deal actually, since i will introduce a 'OO-callback' which performs a message send to an object using certain selector, it will be possible, of course, to pass the signature using other way, i.e. as an extra argument:
callback := 3 createCallbackFor: #+ signature: #( int (int arg) )
as well as:
callback := 3 createCallbackFor: #+
My 2c: Does the behavior differ based on the objects class / dynamic state? (In other words, are there multiple implementors? Does it use private instvars to accomplish it?)
private ivars? where? the callback implementation is 'private', since it is full of dark voodoo assembly code ;) , but its public interface won't put any special requirements to the object(s) to form a callback. It is just a message send, after all, so you just stating that when this callback get called a given object should receive that message (with callback arguments converted to smalltalk objects , of course), no more no less. The only thing with this kind of callbacks is their lifetime: it will be disposed once a receiver will turn to be garbage, which is logical, since it cannot longer receive any messages. But it even more convenient, because you don't need to keep the callback object around and manage its lifetime by yourself :) The only additional bounds, which make sense to put is to direct the type names/resolution via receiver's class, as well as different code generation options, which it may need. So, you don't have to globally define custom types (or be limited to use only already existing global types/aliases) Like that, if in scope of your class you have an alias 'foo' -> 'int' , you can use 'foo' as a type name in callback signature for an instance of that class.
If not, then I think 'OO-style' is a less elegant solution than keeping that functionality/responsibility in a class more intricately linked to what it is used for.
The march towards the omniprescent Object which knows all, sees all and does all is tangible, and, at least in my eyes, undesirable :)
well, it is just a convenience protocol for Object, allowing any object(s) to form callbacks. like currently we having #nbCall: , in same way i will add #nbCallback:... protocol (or something like that).
Cheers, Henry
-- Best regards, Igor Stasenko.
participants (4)
-
Henrik Sperre Johansen -
Igor Stasenko -
Marten Feldtmann -
Stéphane Ducasse