macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Objective-C: Dynamite!
Pages: 1, 2

Using Dynamic Typing

Adding types to languages is an attempt to provide some guarantee that data objects will not be interpreted incorrectly--a float treated as an integer, a Square as a Socket, and so on. However, making a strict guarantee of this sort is undecidable; that is, no program (or finite chain of reasoning) can detect such bugs reliably. It will either reject some correct programs or accept some buggy ones, or it will go into an infinite loop and never tell us.



How then do type-checking compilers work? They have to make a Faustian bargain: they are guaranteed to terminate with an answer, but that answer is sometimes wrong. And for safety's sake, they must always err on the side of excessive safety: they will reject some programs that are type-safe and would never crash due to a type error.

Here's an example of the sort of design made simpler by dynamic typing. I often write code in which I want to send a message from one object to another when some event occurs. This is an application of the Observer design pattern. A typical C++ solution looks like this:

// An abstract class declaring the interface.
struct Observer {
  void stuffHappened() = 0;
};

// Implementing the interface.
class Whatever : public Observer { ... };

// The interface of the Sender.
class Sender {
  Observer* observer;
  void someMethod();
};

// Implementation of Sender.
void Sender::someMethod {
  // ... stuff happens ...
  observer->stuffHappened();
};

Now, this is a reasonable solution for the problem, but as you see it here, it's a fait accompli. You have already:

  • decided that the observer gets informed via one callback method,
  • specified the callback method name, hence constrained the interface the observer has to support,
  • declared that interface, and
  • declared a real class that supports the interface.

The interface is a form of coupling between the Sender and Observer. Those two classes may just be in separate packages, or the Sender may be in library code from another vendor, and you are supplying the Observer. In either case, both sides have to know about the interface declaration.

This looks good as a finished design, but it's hard to arrive at such a design when you have to work this way. Interface designs change, method names fluctuate, both Sender and Observer may decide to send or observe different things. In other words, with static typing you have to know your solution before you get started.

The Objective-C way of doing this is:

// The interface of the Sender.
@interface Sender {
  id observer;  // Some kind of object.
  -(void) someMethod;
@end

// Implementation of Sender.
@implementation Sender
  -(void) someMethod {
    // ... stuff happens ...

    // Obj-C syntax for same method call.
    [observer stuffHappened];
    }
@end

What's the difference? Here you have only specified the Sender. You don't waste any time specifying the interface the Observer has to support. You have declared it as type id, which means, "some kind of class (that is, not a built-in int), but we're not being any more specific." In particular, you won't know until runtime whether the object really does support the stuffHappened message. Objective-C lets you customize the handling of (otherwise) unhandled messages: this can range all the way from nothing at all to rerouting the message to a delegated substitute object to terminating the program with a runtime error.

The Case for Dynamic Typing

So what? Doesn't this amount to so much backsliding from the hard-won lessons of data typing? Why is this not just an example of lazy programming?

There's the reasoned response, which goes like this: During development, a lot of design issues are in flux. It's a lot easier to work out how your framework is supposed to behave without having to start out in a most-factored state, with plenty of empty interface fluff. In fact, Objective-C lets you use static typing as well, so once you settle on a design, you can declare your types with no penalty. Best of both worlds.

Then there's the theoretical response: Objective-C objects are more flexible because of this tolerance for unrecognized messages. In effect, they are already implementers (in an empty sense) of an infinite number of as-yet-undesigned interfaces. You can plug them into all sorts of designs, and if they have anything to offer, any service to provide, they will provide it; otherwise they will just get out of the way.

Finally there's the radical response. Program safety is not decidable anyway. In any nontrivial program there are many bugs, among which type errors are a small subset; and an unhandled message may not even be a bug. And in any case, Objective-C provides a mechanism for intercepting and either discarding or redirecting unhandled messages. If it really is a bug, wouldn't you prefer logging an error to calling some unexpected (and definitely wrong) method, as you would do in C++ if you called a method on an object that doesn't have it? (You would have to use casting to do this, to get around the compiler's static type-checking, of course.)

This sort of design often crops up in GUI frameworks, where it's tedious (at best) for the designer to try to anticipate all the kinds of messages, and their Senders and Observers. Instead, buttons, sliders, and all the kinds of custom controls that make GUIs useful can just accept a generic Observer. For a good example of how Objective-C's dynamism can be used to create elegant program frameworks, look at Apple's Cocoa library, in particular the Application Kit and its Responder Chain.

In summary, relaxing the iron grip of static type-checking lets you write simpler, more elegant program architectures. Objective-C gives you the option of enforcing the law where you think corporate polluters will do the most harm, but looking the other way in coffeehouses where creative freedom is needed. To be sure, this is a double-edged sword; you can find more extensive discussion here.

Andrew M. Duncan started programming in FORTRAN on Control Data 6600 hardware in 1974, and a quarter century later progressed to Mac OS X.


O'Reilly & Associates recently released (December 2002) Objective-C Pocket Reference:


Return to the O'Reilly Network.