How’s the poll going?

顺颂商祺
陈北宗 Max Chan, from SushiBits Projects
Tel. +86 186-2165-8748
https://github.com/SushiBits

> On Nov 26, 2019, at 17:55, Gregory Casamento <[email protected]> wrote:
> 
> 
> 
> I'd really like some resolution on this topic.   There seem to be a lot of 
> reasons for and against.
> 
> GC
> 
> 
>> On Mon, Nov 25, 2019 at 1:04 PM David Chisnall <[email protected]> 
>> wrote:
>> On 25 Nov 2019, at 14:07, H. Nikolaus Schaller <[email protected]> wrote:
>> > I am not sure that this is the only way to implement it.
>> > 
>> > First of all the callMethodOn returns some block which is a data structure 
>> > knowing that it should take the parameter x and do some function.
>> > Let's call it NSBlock. NSBlock can be an ordinary object like any other so 
>> > that it can follow the same memory management rules as used otherwise.
>> 
>> That’s shifting the goalposts somewhat.  It is not news that objects and 
>> closures are equivalent.  Smalltalk implemented blocks as BlockClosure 
>> objects, Ian Piumarta’s Composite Object-Lambda Architecture, and C++ 
>> lambdas (which are just shorthand for C++ objects that implement 
>> `operator()`).  You can always express anything that uses blocks with 
>> objects.
>> 
>> There are two issues:
>> 
>> 1. If you want to be compatible with existing APIs that use blocks, you need 
>> to be ABI compatible with blocks.
>> 2. The reason that most languages that have objects also have blocks is that 
>> the shorthand syntax is very convenient.
>> 
>> The following are roughly equivalent:
>> 
>> ```
>> @interface Delegate : NSObject
>> - (void)invoke;
>> - (instancetype)initWithCapture: (id)someObject;
>> @end
>> 
>> @implementation Delegate
>> {
>>         @private
>>         id obj;
>> }
>> - (instancetype)initWithCapture: (id)someObject
>> {
>>         if ((self = [super init]) == nil) return nil;
>>         obj = [someObject retain];
>>         return self;
>> }
>> - (void)invoke
>> {
>>         [obj doSomething];
>> }
>> - (void)dealloc
>> {
>>         [obj release];
>>         [super dealloc];
>> }
>> @end
>> 
>> // At construction site:
>> 
>> [[Delegate alloc] initWithCapture: x];
>> 
>> // At use site:
>> 
>> [delegate invoke];
>> ```
>> 
>> And this, with blocks:
>> 
>> ```
>> // At construction site:
>> 
>> ^() { [x doSomething]; };
>> 
>> // At use site:
>> 
>> delegate();
>> ```
>> 
>> At use, these are similar complexity for the programmer.  At the point of 
>> construction, one is one line of code (two or three if you put lambda bodies 
>> on their own lines), the other is 26.  As a programmer, I don’t want to 
>> write 26 lines of code for a one-line callback.
>> 
>> In C++98 you could probably template that and provide a generic class that 
>> took a struct containing the captures and a C function, so you’d get a lot 
>> less boilerplate.  Assuming you had fudged ARC like this (as above, this 
>> code is typed into a mail client and probably doesn’t compile):
>> 
>> ```
>> template<typename T>
>> struct ObjCObjectWrapper
>> {
>>         ObjCObjectWrapper(T x) : obj(objc_retain(x)) {}
>>         ObjCObjectWrapper(const ObjCObjectWrapper &other) : 
>> obj(objc_retain(other.obj) {}
>>         ObjCObjectWrapper(ObjCObjectWrapper &&other) : obj(other.obj)
>>         {
>>                 other.obj = nil;
>>         }
>>         ObjCObjectWrapper()
>>         {
>>                 objc_release(obj);
>>         }
>>         operator=(T x)
>>         {
>>                 objc_storeStrong(&obj, x);
>>         }
>>         T operator()
>>         {
>>                 return obj;
>>         }
>>         private:
>>         T obj;
>> 
>> };
>> ```
>> 
>> You could then define a generic capture structure and invoke method like 
>> this:
>> 
>> ```
>> template<typename Capture, typename Ret, typename... Args>
>> struct BlockImpl
>> {
>>         using invoke_t = Ret(*)(Capture &, Args...);
>>         void operator()(Args... args)
>>         {
>>                 inv(capture, std::forward<Args>(args)…);
>>         }
>>         Block(Capture &&c, invoke_t fn) : capture(c), inv(fn) {}
>>         private:
>>         Capture capture;
>>         invoke_t inv;
>> };
>> ```
>> 
>> This is then generic and you could use it as follows:
>> 
>> ```
>> struct CaptureOneObject
>> {
>>         ObjCObjectWrapper<id> o;
>> };
>> void invoke(CaptureOneObject &c)
>> {
>>         [(id)c.o doSomething];
>> }
>> // At construction site:
>> std::function<void(void)> block(BlockImpl<CaptureOneObject, void>({x}, 
>> invoke));
>> // At use site:
>> block();
>> ```
>> 
>> I *think* you could get the same ABI as blocks if you worked on the generic 
>> templated boilerplate a bit.
>> 
>> Of course, if you were using C++ then you could also write it using lambdas 
>> as:
>> 
>> ```
>> // At construction site
>> ObjCObjectWrapper<id> capture(x);
>> auto block = [=capture]() { [(id)capture.o doSomething]; };
>> // At use site:
>> block();
>> ```
>> 
>> And with this you don’t need the invoke function or the capture class.  
>> Again, much less boiler plate for users, though we don’t have ABI 
>> compatibility with blocks.  
>> 
>> If you were using ARC and C++, then this reduces even further to:
>> 
>> ```
>> auto block = [=]() { [x doSomething]; };
>> ```
>> 
>> And now we’re back with different syntax for the same thing, though with a 
>> different ABI (I think Clang has support for implicitly converting C++ 
>> lambdas to blocks, but it’s been a few years since I tried)
>> 
>> David
>> 
>> 
> 
> 
> -- 
> Gregory Casamento
> GNUstep Lead Developer / OLC, Principal Consultant
> http://www.gnustep.org - http://heronsperch.blogspot.com
> http://ind.ie/phoenix/

Reply via email to