EDIT June 2, 2014: The TERecord implementation described here is not how the current TERecord implementation works. I updated the README on GitHub to show how it currently works.
Ever since discovering Lisp, I’ve felt an almost… loathing for other languages. They make things too complicated. You feel that other languages encourage code that ends up ugly, verbose.. inelegant. This feeling is so well known, it even has a name, the “The Lisp Snob“. Well, I’m quite sorry, but I think it’s too late for me, I’ve succumbed to the snobbery!
It’s possible, however, to bring over some of the ideas from the Land of Lisp to other languages. Indeed this is essentially what has been happening for the past several decades, people just call it something different (“a new language”, or “new features”). Today, I’d like to show what happens when you bring one such idea to Objective-C:
It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.
—Alan Perlis
PXSourceList is a wonderful and popular Cocoa view for creating iTunes-like source lists created by Alex Rozanski:
Unfortunately, the example code that created the screenshot above suffers from a very common programming paradigm called Object Oriented Programming (OOP). The OOP paradigm has dominated Earth’s developers for the past several decades. I used to follow (and
teach!) it, but after learning Clojure, I’ve changed my mind. I now agree with the Alex Rozanski quote above and
Rich Hickey: it is better to have a small number of core data structures (preferably immutable) that are manipulated by hundreds of functions.
For this post, I will focus on just one of the many benefits of dropping the OOP perspective: code brevity.
The chief source responsible for the screenshot above is copied below. My point here isn’t to ask you to actually read through all of it, just note its size.
Instead of this:
#import <Cocoa/Cocoa.h>
@interface SourceListItem : NSObject{
NSString *title;
NSString *identifier;
NSImage *icon;
NSInteger badgeValue;
NSArray *children;
}
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *identifier;
@property (nonatomic, retain) NSImage *icon;
@property NSInteger badgeValue;
@property (nonatomic, copy) NSArray *children;
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier;
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier icon:(NSImage*)anIcon;
- (BOOL)hasBadge;
- (BOOL)hasChildren;
- (BOOL)hasIcon;
@end
#import "SourceListItem.h"
@implementation SourceListItem
@synthesize title;
@synthesize identifier;
@synthesize icon;
@synthesize badgeValue;
@synthesize children;
#pragma mark -
#pragma mark Init/Dealloc/Finalize
- (id)init
{
if(self=[super init])
{
badgeValue = -1; }
return self;
}
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier
{
SourceListItem *item = [SourceListItem itemWithTitle:aTitle identifier:anIdentifier icon:nil];
return item;
}
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier icon:(NSImage*)anIcon
{
SourceListItem *item = [[[SourceListItem alloc] init] autorelease];
[item setTitle:aTitle];
[item setIdentifier:anIdentifier];
[item setIcon:anIcon];
return item;
}
- (void)dealloc
{
[title release];
[identifier release];
[icon release];
[children release];
[super dealloc];
}
- (void)finalize
{
title = nil;
identifier = nil;
icon = nil;
children = nil;
[super finalize];
}
#pragma mark -
#pragma mark Custom Accessors
- (BOOL)hasBadge
{
return badgeValue!=-1;
}
- (BOOL)hasChildren
{
return [children count]>0;
}
- (BOOL)hasIcon
{
return icon!=nil;
}
@end
#import "AppDelegate.h"
#import "SourceListItem.h"
@implementation AppDelegate
#pragma mark -
#pragma mark Init/Dealloc
- (void)awakeFromNib
{
[selectedItemLabel setStringValue:@"(none)"];
sourceListItems = [[NSMutableArray alloc] init];
SourceListItem *libraryItem = [SourceListItem itemWithTitle:@"LIBRARY" identifier:@"library"];
SourceListItem *musicItem = [SourceListItem itemWithTitle:@"Music" identifier:@"music"];
[musicItem setIcon:[NSImage imageNamed:@"music.png"]];
SourceListItem *moviesItem = [SourceListItem itemWithTitle:@"Movies" identifier:@"movies"];
[moviesItem setIcon:[NSImage imageNamed:@"movies.png"]];
SourceListItem *podcastsItem = [SourceListItem itemWithTitle:@"Podcasts" identifier:@"podcasts"];
[podcastsItem setIcon:[NSImage imageNamed:@"podcasts.png"]];
[podcastsItem setBadgeValue:10];
SourceListItem *audiobooksItem = [SourceListItem itemWithTitle:@"Audiobooks" identifier:@"audiobooks"];
[audiobooksItem setIcon:[NSImage imageNamed:@"audiobooks.png"]];
[libraryItem setChildren:[NSArray arrayWithObjects:musicItem, moviesItem, podcastsItem,
audiobooksItem, nil]];
SourceListItem *playlistsItem = [SourceListItem itemWithTitle:@"PLAYLISTS" identifier:@"playlists"];
SourceListItem *playlist1Item = [SourceListItem itemWithTitle:@"Playlist1" identifier:@"playlist1"];
SourceListItem *playlist2Item = [SourceListItem itemWithTitle:@"Playlist2" identifier:@"playlist2"];
SourceListItem *playlist3Item = [SourceListItem itemWithTitle:@"Playlist3" identifier:@"playlist3"];
[playlist1Item setIcon:[NSImage imageNamed:@"playlist.png"]];
[playlist2Item setIcon:[NSImage imageNamed:@"playlist.png"]];
[playlist3Item setIcon:[NSImage imageNamed:@"playlist.png"]];
SourceListItem *playlistGroup = [SourceListItem itemWithTitle:@"Playlist Group" identifier:@"playlistgroup"];
SourceListItem *playlistGroupItem = [SourceListItem itemWithTitle:@"Child Playlist" identifier:@"childplaylist"];
[playlistGroup setIcon:[NSImage imageNamed:@"playlistFolder.png"]];
[playlistGroupItem setIcon:[NSImage imageNamed:@"playlist.png"]];
[playlistGroup setChildren:[NSArray arrayWithObject:playlistGroupItem]];
[playlistsItem setChildren:[NSArray arrayWithObjects:playlist1Item, playlistGroup,playlist2Item,
playlist3Item, nil]];
[sourceListItems addObject:libraryItem];
[sourceListItems addObject:playlistsItem];
[sourceList reloadData];
}
By using “special” dictionaries, we can greatly reduce code size, and enhance flexibility
Clojure has the concept of “records”. These are basically “special dictionaries” (AKA maps). Clojure uses maps/dictionaries all over the place, because it realizes that it’s not only not necessary to create a new class for every type of data you have, but it’s counter-productive.
Today I spent some time adapting the idea of Clojure’s records into its Objective-C equivalent simply through the creation of categories on the NSDictionary and NSMutableDictionary classes. The result is that I was able to reproduce the screenshot above with the following code (equivalent sections to the ones above):
#import "AppDelegate.h"
@protocol SourceListItem
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *identifier;
@property (nonatomic, retain) NSNumber *type;
@property (nonatomic, retain) NSImage *icon;
@property (nonatomic, retain) NSMutableArray *children;
@end
@implementation AppDelegate
- (void)awakeFromNib
{
sourceListItems = [[NSMutableArray alloc] initWithObjects:
_MD(@"title", @"LIBRARY",
@"identifier", @"library",
@"children",
NSARY(_MD(@"title", @"Music",
@"identifier", @"music",
@"icon", NSIMG(@"music")),
_MD(@"title", @"Movies",
@"identifier", @"movies",
@"icon", NSIMG(@"movies")),
_MD(@"title", @"Podcasts",
@"identifier", @"podcasts",
@"icon", NSIMG(@"podcasts")),
_MD(@"title", @"Audiobooks",
@"identifier", @"audiobooks",
@"icon", NSIMG(@"audiobooks")))),
_MD(@"title", @"PLAYLISTS",
@"identifier", @"playlists",
@"children",
NSARY(_MD(@"title", @"Playlist1",
@"identifier", @"playlist1",
@"icon", NSIMG(@"playlist")),
_MD(@"title", @"Playlist Group",
@"identifier", @"playlistgroup",
@"icon", NSIMG(@"playlistFolder"),
@"children",
NSARY(_MD(@"title", @"Child Playlist",
@"identifier", @"childplaylist",
@"icon", NSIMG(@"playlist")))),
_MD(@"title", @"Playlist1",
@"identifier", @"playlist1",
@"icon", NSIMG(@"playlist")),
_MD(@"title", @"Playlist1",
@"identifier", @"playlist1",
@"icon", NSIMG(@"playlist")))),
nil];
[sourceList reloadData];
}
Not only is the code above more flexible and significantly shorter, but it’s clearer as well! You can practically visualize the GUI’s structure from the code itself!
The rest of the code (related to the delegate methods) is essentially equivalent, except it’s again, shorter.
“Magic” Dictionaries with Properties!
Dictionaries are created using the _MD and _D macros (for creating mutable and immutable dictionaries, respectively). These are plain old standard Foundation dictionaries. They can be saved to the hard disk, read back, key-value coded, etc. The reason for the macros is code brevity and because they use a new dictionaryWithKeysAndObjects: method that’s added to all dictionaries through the categories (I have no idea why Apple’s engineers throught it would be better to have values come before keys as with dictionaryWithObjectsAndKeys:…).
Next, let’s have a look at how dictionary values are manipulated and obtained. Most semi-intelligent languages have a map literals and simple syntax for fetching and setting map key/value pairs. Objective-C has no such intelligence imparted to it. However, it does have the very nifty forwardInvocation: mechanism. By combining forwardInvocation: with Objective-C protocols and properties, it’s possible to create “Objective-C records”.
Instead of this:
- (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
{
if(item==nil) {
return [sourceListItems count];
}
else {
return [[item children] count];
}
}
We can now use the dot-syntax notation to do this:
- (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id<SourceListItem>)item
{
return item ? [item.children count] : [sourceListItems count];
}
Behind the scenes, item.children is turned into [item objectForKey:@”children”].
Setting values is also done through the standard dot-notation of Objective-C properties:
- (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id<SourceListItem>)item
{
item.title = object;
}
Grab on Github
You’ll find the project for TERecord here, and the converted example of PXSourceList here.
You might be interested bu Nu, a Lisp specifically designed for Objective-C interoperability.
http://programming.nu/about
@Jeff: Yes, thanks for the link, I do know about Nu, and I am very happy that it exists, but the reason I don’t use it though is because it doesn’t compile down to binary (as far as I heard last), and for some projects I prefer they do.
Color me… unimpressed.
First, we could have “cleaned up” the first block of code almost as easily by adding a bunch of macros to it. Eliminate the second version’s magic macros for _MD, NSARY, and NSIMG, and the code starts to become equally dense.
Second, and more to the point, object-oriented programming is about combining data *with* it’s associated functionality.
SourceListItem fails this definition, in that it encodes no object behaviors whatsoever. All we have is — essentially — a bare bones struct with accessors and nothing else.
Refactoring the SourceListItem init method to take an icon image name and not an image could have cleaned up a full third of the list building code.
Implementing an addChildWithName: … method would have cleaned up another third.
And that’s not even counting polymorphic functionality we could add to specific types on the rendering side of the equation.
I see your point regarding dictionaries, but comparing a marco-laden special case to code that exemplifies bad OOP practice in the first place…
Think you used enough dynamite there, Butch?
Clozure CL integrates with Cocoa and can be compiled. Also works with Cocotron on Windows.
http://www.clozure.com/clozurecl.html
One interesting CCL based project is http://code.google.com/p/xmlisp/
CCL might be overkill for a small project but it is actively being developed for use with Cocoa and Cocotron.
@Michael:
That was… exactly the point. I was making the case against “combining data with its associated functionality”. I did not attempt to make the full argument against it, for that feel free to look into Clojure and some of Rich Hickey’s talks (and there are plenty of others who go into it in more depth as well).
@Christopher: thanks for the link! I had a look over it but it’s not quite what I’m looking for (I’m also not a huge fan of Common Lisp). My wish would be for something along the lines of Clojure for Cocoa, and preferably backed by Apple. Probably won’t ever happen.
And as I pointed out, pointing to a piece of macro-enabled code and comparing it to a bad piece of OOP code and saying that the macro-infested code is shorter proves little.
Reading some of Clojure’s goals, I finally found the following:
“Dispatches via static, dynamic or external properties, metadata, etc..”
Which is how Clojure gets around OOP polymorphism… by duplicating polymorhpism. In effect, Clojure eliminates a polymorphic class hierachy with classes and methods, by implementing a boatload of data-driven dispatch methods that jump to a bunch of specially-named methods distributed throughout your code. (One of the things that OOP tries to avoid.)
SourceListItem, above, would have benefited greatly from subclassing and defining associated functionality for PlayListItem and PlayListGroup. And that, in turn, would have simplified the list building code greatly.
But the developer stopped before that point, allowing you to setup a nice little strawman that could be knocked down in order to show Clojure’s “superiority”.
Speaking of sample code, here’s how I would have done part of the above….
{
sourceList = [[SourceListGroup alloc] init];
SourceListLibrary * library = [sourceList addChildWithClass:@"SourceListLibrary"];
[library addChildWithClass:@"SourceListMusic"];
[library addChildWithClass:@"SourceListMovies"];
[library addChildWithClass:@"SourceListPodcasts"];
[library addChildWithClass:@"SourceListAudiobooks"];
SourceListPlaylists * playlists = [sourceList addChildWithClass:@"SourceListPlaylists"];
[playlists addChildWithClass:@"SourceListPlaylist" andTitle:@"Playlist1"];
SourceListPlaylistGroup * sublist = [playlists addChildWithClass:@"SourceListPlaylistGroup" andTitle:@"Playlist Group"];
[sublist addChildWithClass:@"SourceListPlaylist" andTitle:@"Child Playlist"];
[playlists addChildWithClass:@"SourceListPlaylist" andTitle:@"Playlist2"];
[playlists addChildWithClass:@"SourceListPlaylist" andTitle:@"Playlist2"];
[playlists addChildWithClass:@"SourceListPlaylist" andTitle:@"Playlist3"];
[sourceList reloadData];
}
With smarter objects, the list building code is MUCH shorter, yes? No need to pass in icons, as each class already knows which icon it’s supposed to display. For many, no need to pass in titles, which should be loaded from internationalization strings anyway.
And no macros or oddball syntax to trip up new developers who have no clue as to how your Clojure add-on works. Just the classic OO design methodology… that they already know.
For whatever reason I decided to revisit these comments again; you should check out how TERecord evolved after this post. More elegant then using classes, and no macros to speak of:
https://github.com/taoeffect/TERecord
It’s being used in production in Espionage. 🙂
@Michael Long
>“Dispatches via static, dynamic or external properties, metadata, etc..”
>Which is how Clojure gets around OOP polymorphism… by duplicating polymorhpism. In effect, Clojure eliminates a polymorphic class hierachy with classes and methods, by implementing a boatload of data-driven dispatch methods that jump to a bunch of
Clojure does not “get Around polymorphism it dilivers it A la Carte when you need it, you don’t have to carry it around all the time. You can have a any function for dispatch witch is much MUCH more powerful that your singel dispatch OO (bad C++ style OO) has (if you want your single dispatch just provide “type” as a function). The thing you talk about is called Multimethods and its a very powerful feature that provides multible dispatch and solves the expression-problem in away that is imposible with Java/C++.
What does “implementing a boatload of Data-driven dispatch methods”. Clojure implements nothing you can just provide a function an it can be driven by whatevery you want. What do you mean by Data-driven on what else would you dispatch you class hirarchy is data too.
If you really want to have a hirarchy you can implement one clojure provieds this. You can even implement multible hirarchies.
> … specially-named methods distributed throughout your code. (One of the things that OOP tries to avoid.)
So its much better to use the visitor pattern then? The Visitor pattern does not provide all the features and it does not solve the epxression problem.
> I advise you to really look at Objectsystems that are not clones of C++. Here are some pointers.
The Dylan Programming Langauge: http://www.opendylan.org/, http://en.wikipedia.org/wiki/Dylan_%28programming_language%29
A little extra information: Apple funded dylan and it should have become THE langauge for programming on the apple but back when the had money problems the obandend it. With a little more luck on the dylan side you would programm dylan today and not Objectiv C.
Alsow look at the CLOS (probebly the most powerful object system ever)
@Michael: That’s definitely an improvement, but (1) please show the equivalent code, you left out several files (one for each of those classes). (2) If you view OOP to be the superior approach, fine, stick with it, do what works best for you. If you haven’t actually given Clojure a serious try though, I recommend it, you might change your mind (as I did).
I recommend the book “The Joy of Clojure”.
@nickik: “…really look at Objectsystems that are not clones of C++…”
Objective-C is not a “clone” of C++. Both languages grew up during the same period, and ended up branching out into their own domains. C++ went down the rabbit hole with the STL and the language’s penchant for allowing you to overload anything and everything.
Objective-C, OTOH, is really an attempt to clone Smalltalk’s object and message passing model onto C. Both choose to build off the existing C compilers of the day, much as Clojure builds off existing JVMs.
Stevie liked OC and used it for NeXT, while Microsoft leaned towards C++ and used that to implement their MFC library. The rest, as they say, is history.
Dylan is interesting, and I’m not saying Clojure doesn’t have some cool concepts.
But my primary point remains: Objective-C is not Clojure. If you want to program in that, or Dylan, or LISP, or whatever, then feel free.
But bolting on additional language features onto Objective-C? OC is already fairly complex and, as I said earlier, you’re implementing a bunch of program functionality in a non-standard way. That’s going to ramp up an already steep learning curve for new hires and for those who need to maintain your code.
Finally, and again to repeat, the sample given doesn’t fly. A tree of similar objects that require different behaviors? That’s a classic OO design pattern.
But showing an incomplete OO design, and then comparing that to “improved” code that serves to illustrate only one aspect of the system at hand is to setup that straw man I spoke of earlier.
Type-specific behaviors must exist SOMEWHERE, and in OO we combine them into tight back boxes. Dispatching multiple behavior methods throughout the code might seem easier, but comes at its own cost.
TANSTAAFL
@greg: “…but please show the equivalent code…”
I’ll show mine if you show yours. (grin) I mean, at least the original SourceListItem example you provided was fully functional.
Where are your macro definitions? Where are your NSDictionary and NSMutableDictionary category additions? Where’s the code that walks the dictionary definitions, and actually creates the SourceListItems that PXSourceList expects to see?
How much code to I need to port from project to project to add this functionality. In a team effort, what’s the learning curve needed to bring everyone else up to speed?
Based on all of those things, the “Clojure” example fails to deliver.
As you yourself just pointed out, it’s easy to demonstrate the superiority of “code brevity” if you hide all of your implementation details.
@Michael:
I did show you mine. See the two links at the bottom of the post?
The source I gave was equivalent in functionality to the original quoted source (OK, maybe I forgot to include about 2 lines for the macros. Sorry.).
The point is that once you have the basic fundamentals done (the categories, which I’ve written for you), you don’t need to do anything except specify a protocol. I do not consider the fundamentals necessary to show, because they are coded only _once_. You have to create a new class for each type you want, which involves creating 2 new files per class. Each class has to have getters and setters written for it, internal instance variables, etc. Each class is unique to a high degree and makes it much more difficult to apply functions to it.
Using the alternative presented, all that’s necessary is the specification of a Protocol. That’s it.
As mentioned, no one is forcing you to use this. Do what feels right for you.
What’s non-standard in one language or paradigm, is standard in another. Whether something is “standard” or not should not be the sole deciding factor in your decision to do something. That’s called herd mentality, and often it leads to really bad results.
All that’s being done here the introduction of a cleaner paradigm to writing code in Objective-C, one that’s simpler, more flexible, and easier to understand than the one it’s replacing. That’s a win-win, I think.
“You have to create a new class for each type you want, which involves creating 2 new files per class. Each class has to have getters and setters written for it, internal instance variables, etc.”
The base class needs those, correct. As to the rest, who knows? Perhaps I’m using the class names to simply set internal parameters in a single object. That’s an implementation detail. But as long as I fulfill the contract defined by the interface, To the user, it’s all good.
But you’re right, because for the moment, it doesn’t jibe. You’re using a bunch of macros to create nested arrays and dictionaries, which could just as easily have been instantiated by reading an equally simple PLIST or from an XML definition “protocol”.
It makes it simple to define structures, true, but throws out type-safety and object-specific behavior for something to be specified later on.
Tell you what. Do the next article, and show the “later on”. Show some of those “hundreds of functions” that will operate on the data.
Show how you’re going to enforce type-specific behavior, such as enforcing the rule that only libraries and groups can have children.
Then we’ve got something more to talk about…
@Michael: There may indeed be a followup post at some point. Stay tuned. 🙂
Not sure why you’re worrying about type safety, it’s there (the protocols provide it).