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.