discuss-gnustep
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Which ObjC2.0 features are missing in the latest GCC?


From: Max Chan
Subject: Re: Which ObjC2.0 features are missing in the latest GCC?
Date: Tue, 26 Nov 2019 18:54:31 +0800

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 <greg.casamento@gmail.com> 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 <gnustep@theravensnest.org> wrote:
On 25 Nov 2019, at 14:07, H. Nikolaus Schaller <hns@goldelico.com> 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

[Prev in Thread] Current Thread [Next in Thread]