Frank Heckenbach wrote:
Terminology aside, I still think that protocols can be mapped to "pure abstract" C++ classes (of course, resolving a name conflict between a protocol and a class with the same name), and then the NSObject protocol would be implemented in (Objective-C) or inherited from by (C++) any other protocol and class.
"conformsTo:" would then correspond to an inheritance test (like "is" in Pascal, or trying a dynamic_cast in C++), in the special case that the right operand is a (C++ class that corresponds to an Objective-C) protcol.
- Keep the differences as small as possible (ideally: Objective-C
class -> C++ class with an additional, auto-generated dispatch method).
As I had previously mentioned, it is my understanding that the aim of this part of the discussion was to explore how an interface for GPC to make use of ObjC libraries might be designed and implemented, what the caveats and challenges are etc.
For such an interface, whatever mapping takes place would have to be undertaken in the direction from Pascal *towards* ObjC and at a lower level from GPC's intermediate language *towards* ObjC.
Now, for the sole purpose of a person unfamiliar with ObjC but familiar with C++ to gain a better understanding of ObjC, I can see some value in articulating how certain ObjC semantics might conceivably be mapped in the opposite direction.
However, in order to design and build a useful and convenient interface for using ObjC libraries from within another language, the designers/implementors should be able to think in the ObjC paradigm space. Ideally they would have implemented something meaningful in ObjC/Cocoa/GNUstep.
At this stage of the discussion, I find it increasingly difficult to tell whether the articulation of conceivable mappings of ObjC to C++ is actually meant to be for the sole purpose of gaining a better understanding of ObjC itself.
More often than not, it would seem the scope has moved confusingly close to discussing how a translator from ObjC to C++ might be designed. In order not to add to this confusion I will keep my responses to only those items of the discussion that I can clearly identify as GPC=>ObjC mapping issues.
- Use compiler switches, as GPC already does for the 4 supported
object models.
I personally prefer the language design philosophy where pragmas do not change the meaning of source code, but yes, the use of pragmas is certainly a workable approach to switching between object models.
- Actually support Objective-C syntax (if it doesn't cause too many
syntax conflicts), whether or not one is trying to keep compatible objects.
There is an increasing number of languages that follow this approach. It has its advantages but some people argue that the source code is then no longer portable to compilers that do not support the syntax.
Yet, in order to remain source code compatibility with non-ObjC interfacing dialects of the host language, there must be a complete mapping of existing syntax for the native object model to the ObjC object model. If only one piece of additional syntax is added, compatibility is lost.
As it is often difficult to map existing syntax to all the required semantics of a foreign object model, one might hold the view, if we are not going to be compatible anyway, we might as well support ObjC syntax if it makes the task easier.
Of course there is always an element of what might be described as "language esthetics". People programming in Pascal will usually prefer a more Pascal looking syntax to target ObjC APIs over verbatim ObjC.
For example, ObjC distinguishes between class methods and instance methods ...
+foo; /* class method declaration */ -bar; /* instance method declaration */
In Pascal, you could of course support this syntax verbatim, but you might want to make it at least a little more Pascal-like ...
procedure +foo; (* class method declaration *) procedure -bar; (* instance method declaration *)
or still more Pascal-like ...
class procedure foo; instance procedure bar;
or even ...
class method foo; instance method bar;
The key is to find the right balance. Where there is a significant benefit of using ObjC syntax or syntax that closely resembles ObjC, one should probably not shy away from doing so. But where there is no such benefit, one might want to use syntax that is as Pascal-like as possible.
In my experience, the area where it makes most sense to use Smalltalk or ObjC syntax (or something close) is the message passing.
Objective-C has four visibility modes for instance variables: public, package, protected and private. One of those can be made default, for the other three some kind of qualifier will be needed. Possibly such qualifiers already exist in the native syntax.
Except for package (though in BP, private actually means package (unit) wide visibility, not entirely private).
If you allow switching between object models, then it shouldn't matter that the semantics of "private" in one model do not exactly match the semantics of "private" in another model. Clearly in a multiple-object-models language, the programmer needs to be aware of the specifics of each object model he wishes to use in his code.
If there is no reserved word for "package" in GPC to mark the corresponding visibility mode of the ObjC object model, it could either be added (and only be available in when the ObjC object model is active) or another reserved word might be reused in its place, for example "unit".
A very important feature in Objective-C is the ability to add a method to a class outside of the scope of the compilation unit where the class is declared. In Smalltalk and Objective-C this is called a category.
IIUC, this works retroactively, i.e. if a module A calls a method of (sends a message to) an object declared in a module B, and module C extends that object via a category, even if neither A nor B know about C, it affects this call/message, right?
Yes, at runtime all methods are equal, there is no difference between those declared within the scope where the class itself is declared and those declared outside that scope. Categories are *lexical* extensions only. They allow spreading methods *lexically* over multiple files. At runtime, the lexical separation becomes invisible.
- Make Send a method (at least syntactically) that returns a
reference to the object, so calls wouldn't nest as much:
foobar.Send("setFoo:", 123) .Send("rotateLeft:", 456) .Send("splitAt:", 789) .Send("shuffle"); foobar.Send("setFoo:", foo.Send("count")) .Send("rotateLeft:", barbaz.Send(bam.Send("boo:", 123)));
Like I said, there are conceivably many other ways to design syntax for message passing. At the end of the day it comes down to striking the right balance of what users will find esthetically acceptable and what is practical. In any event, the message passing deserves to be given more thought and effort than other aspects of interfacing to ObjC.
Objective-C doesn't have templates nor does it require them because you can choose between static and dynamic typing.
^[citation required]
...
We've had this claim in this thread before. I asked the question, which is still unanswered: How do you implement a generic list type with the following properties:
...
So far this question has been ignored. Until I see a valid response, I will ignore further claims that templates are not needed.
This all comes down to different paradigms.
When different paradigms are discussed, it is easy to mistake statements of the form "A is *different* from B" with "A is better than B" and this often leads to a discussion of different paradigms to slide into an argument about personal preferences. I will refrain from debating preferences.
Suppose I said "When eating soup, the Japanese do not require a spoon because they drink their soup from a bowl". Would anybody pick that apart along the lines of "Unless somebody can tell me how the Japanese eat their soup if they don't have a bowl at hand, then I will simply ignore this statement and conclude that the Japanese do require a spoon to eat their soup just like I do."?
No matter what the pros and cons of eating soup one way or the other way may be, the fact remains that one way a spoon is required, and the other way a spoon is not required.
A more technical analogy would be algebraic versus reverse polish notation in electronic calculators. RPN calculators do not require parentheses because they use postfix operators that operate on a stack of intermediate results.
No matter what the pros and cons of algebraic versus reverse polish notation may be, the fact remains that RPN does not require parentheses.
Objective-C is a late bound language, C++ is early bound. That is a major difference in paradigm and as a result certain problems are solved using entirely different approaches. No matter what the pros and cons of early versus late bound may be, the fact remains that late bound languages do not require templating for generics.
Besides, you were missing the point which was this: Because ObjC is late bound, you do not require any mappings for templating syntax because there is no such thing.
In Objective-C constructors and destructors are just messages, so there is no need for special syntax and translations.
foo := [[FooClass alloc] initWithFoo: 123];
The emphasis was on automatic. So can you, as a class designer:
- Enforce certain actions that are always done on instantiation
(e.g. ensure that certain object fields are always initialized to certain values, which don't have to be constant, but might be a running index or something). I imagine this might be possible by implementing an instantiation method in the class that does this, but I'm not sure.
Indeed.
By convention, ObjC classes have a method for allocation (called "alloc") and at least one method for initialisation (called "init"). These methods are by default inherited from the super-class. However, you can of course override them with class specific implementations. Typically, a class specific implementation of init would first invoke the super-class' init method and then carry out any class specific initialisations thereafter.
In any event, a class is always instantiated by sending and alloc message to it and then sending an init message to the newly allocated object. There is no special syntax.
Thus, when you see a class instantiation such as ...
foo = [FooClass alloc] init];
if you did implement a FooClass specific init method, then the init message will invoke that method
if you did not implement a FooClass specific init method, then the init message will invoke the init method inherited from the FooClass' super-class.
Overriding an inherited method is as simple as declaring and implementing the method just like any other method.
- Enforce certain actions that are always done on deletion of an
object, whichever way this happens.
Same principle as with initialisation.
Again by convention, ObjC classes have a method for retaining an object (called "retain") and a method for releasing and object (called "release"). In addition there is also a method called "autorelease". Again, these methods are inherited from the super-class.
When you send a retain message to an object, its reference count is incremented, when you send a release message to it, its reference count is decremented. If as a result of your release message the reference count goes to zero, the object is deallocated. Thus, even if you send a release message, should there be still other objects who retained the object, it will not immediately be deallocated but only when the last retaining object sent a release message.
Again, you can override the inherited methods with class specific implementations that perform additional tasks.
E.g., if an object (not a pointer/reference) goes out of scope, its destructor is called; if a container (list, map, ...) is destroyed, for all of its contents their destructor is called, etc.
You can instantiate a class and send it an autorelease message ...
foo = [FooClass alloc] init] autorelease];
in which case it will be managed by a so called autorelease pool that will deallocate the object when it goes out of scope.
In any event, anything that happens to an object during its lifetime from allocation to deallocation happens to it by sending messages to it which invokes methods implemented in the object's class or any of its super-classes (up to the root class). Any of these methods can be overridden. Any of these messages can be intercepted and redirected. The extend of your control is only limited by your knowledge of the API. There is no hidden magic anywhere that only the compiler has access to. Everything is accessible and controllable from within your own ObjC code.
For the avoidance of confusion I should perhaps mention that when you see other messages being used to instantiate, for example ...
foo = [FooClass new];
or
foo = [FooClass newWithBar: 123 andBaz: 456];
or
foo = [FooClass newFromArray: bar];
these are messages implemented by methods on top of alloc and init. For example method new would invoke alloc and init internally. In other words it is just a convenience method. It is customary for class implementors to provide several newWith... or newFrom... methods as they see fit.
[fooException raise];
Does this do what raising exceptions in other languages do, i.e. "jump" to the nearest enclosing appropriate catch-block? Or does it just send the message and continue with the next statement?
Depending on how your exception handling code handles the exception. It may resume, it may re-raise or raise another exception or it may abort. Whatever you see fit.
However, the primary reason for providing an Objective-C interface is usually to be able to use the Cocoa or GNUstep APIs. For that, you will need to interface with the Objective-C runtime library.
It seems so. (Which also means, since I have no real interest in either of them, I should probably get out of this discussion and leave it to those who might actually implement it.)
Implementing a useful and convenient interface to ObjC is certainly not a minor effort, so nobody can blame you if you don't want to get involved. However, as the primary compiler maintainer, even if somebody else came along to implement such an interface, you probably would want to keep yourself in the loop simply because any such interface would/will have an impact on the compiler as a whole. After all, whatever its exact shape might be, it would/will add an additional object model to the language.