Five Things that Suck About Objective-C and Cocoa
Things have been quiet here in this blog. Too quiet. As such, I’m keeping the name-five-things-you-hate-about-a-language-you-like ball rolling, having seen it rolled with zest and vigor by brian d foy, Titus, Jacob Kaplan-Moss and Vincent.
Without further ado:
Five Things that Suck About Objective-C/Cocoa:
- Syntax for NSString literals. For the uninitiated, in Objective-C code enclosing
"insomnia"in simple double-quotes creates a C-style char[] string; if you wish to use the far more powerful and versatile Objective-C NSString class, you must add an @ (making@"insomnia"). Backwards-compatibility with C is a good and useful thing, but why require more keystrokes to do the most commonly-used thing? I myself last weekend puzzled over a wonderfully non-specific “invalid reciever” error for a long time before realizing that I forgot an @ when sending strings to anarrayWithObjects:method. Aside from that, why use the @-sign as a prefix for NSStrings when it’s used in a plethora of other places, such as@interface,@implementation,@endand@selector? Though 90% of my @-key-presses in Textmate are prefixes to NSStrings, I can’t have a keypress of @ automatically expand to @”” – there are too many other things to do with the poor little @-sign. The oft-neglected | (pipe or vertical bar, I’ve heard both) character is far less disruptive to the flow of typing. - reallyLongAndCamelCasedMethodNamesGetAnnoying. I refer specifically to the lovely
NSWorkspacemethodopenURLs: withAppBundleIdentifier: options: additionalEventParamDescriptor: launchIdentifiers:
And ObjC method names can be concise yet informative – take for example NSString’scompare: options: range: locale:. I must admit, this complaint is not entirely valid, especially considering Textmate/XCode’s fancy code completion. - No operator overloading. Come on, guys – why reject this crucial part of Smalltalk heritage? I, for one, am sick of writing
objectAtIndexandobjectForKey:as compared to Python’s []. Though Smalltalk allows one to define new operators, I’d be perfectly happy to settle for a few overloadable operators (string concatenation is desperately needed). - Mysterious helper methods. I didn’t know of the existence of
NSHomeDirectory(),NSTemporaryDirectory(), orNSClassFromString()until very recently. True, this is my fault, but I think that F-Script’s idea of storing all of these methods in a singleton System object is excellent, and much more in line with Objective-C’s Smalltalk heritage. (Actually, I have a half-finished ObjC class that makesNSBeep()and all those other miscellaneous C functions into class methods; if there’s any interest, I’ll finish and release it. I suppose that makes this complaint invalid. Oh well.) - File management is a mess. Essential code is scattered throughout
NSWorkspace(in all its brain-dead glory),NSFileManager,NSFileHandle,NSPipe,NSDirectoryEnumerator, andNSData– few things are as infuriating as hunting down the correct class that does exactly what I want. (Actually, no. Finding a better solution after thirty minutes of hacking around some perceived inadequacy is worse.
So there you have it. To be honest, it took me quite a while to write this, mainly because Objective-C is such a great language and Cocoa is such a great set of libraries. I suppose that the imperfections in a consistently useful and friendly toolkits stand out, and in retrospect I sort of feel guilty for my picky attitude. After all, it could be much, much worse.
This Is Worrying
Last login: Sun Mar 18 22:39:27 on ttyp1
Welcome to Darwin!
ok-computer:~ p_trick$ python
Python 2.5 (r25:51918, Sep 19 2006, 08:49:13)
[GCC 4.0.1 (Apple Computer, Inc. build 5341)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True, False
(True, False)
>>> __builtins__.True, __builtins__.False = __builtins__.False, __builtins__.True
>>> True, False # oh dear
(False, True)
>>> if False: print "Oops."
...
Oops.
Is there a reason for this?
Old School
In a raucous debate with my dad about my post regarding the merits of C, he made the following analogy:
“Condemning C because of its poor string support is like condemning a Ford Focus because of its poor ability to cruise at 35,000 feet.”
Touché, Dad. Touché.
An Apology to SQLAlchemy
Dear SQLAlchemy:
Before I started working with you, I became familiar with the Django and SQLObject ORM’s. They use the intuitive technique of assigning attributes to a model, like so:
class Person(SQLObject):
fname = StringCol()
mi = StringCol(length=1, default=None)
lname = StringCol()
This is an intuitive and pleasing paradigm, and I was perfectly content until I heard some clever people say nice things about you. I enjoy working in new environments, so I took a look at you. Much to my suprise, I hated you. Quite strongly, in fact. It felt like taking a giant step backwards going from the above SQLObject code to the following:
person_table = Table(
Column('fname', String(255)),
Column('mi', String(1)),
Column('lname', String(255)))
class Person(object): pass
assign_mapper(Person, person_table)
I recoiled. Why did it take so much more typing in SQLAlchemy to make a far less pythonic solution than that of SQLObject? I then ignored you until I realized that my SQLObject code was littered with list comprehensions instead of good, Right-Thing SQL; I gave you another shot, but I ran you through some more tests.
I realized that you could map multiple tables onto one object and one tables onto multiple objects, and quickly realized that this strange breed of polymorphism/inheritance, alien though it was, totally kicked the crap out of Django’s ORM. (Which, incidentally, still doesn’t support model inheritance.) Declaring tables and objects as one thing, though convenient, leads to sloppy and inefficient code. You made me realize that, and I thank you.
And then the neat ideas started flowing every which way – composite primary keys (why didn’t I think of that?), manual save-and-reload (crucial when just mucking about with data), backreferences (I am Ozymandias, king of kings! Look upon my massive amount of automatically updating references, and despair!), properties (less code to write = good), ActiveMapper (mmm…conciseness), and the way assign_mapper still allows me to treat tables inside objects as attributes.
I owe you an apology, SQLAlchemy. I wronged you.
Sincerely,
Patrick
Why I Dig Objective-C
I apologize if this entry is incoherent or trivial. But I need to build up the habit of blogging.
I recently picked up a copy of Refactoring. I hadn’t heard of that book before I read Steve Yegge’s thoughts on it – indeed, my entire concept of refactoring had previously been limited to Eclipse’s built-in tools. To describe this book as an eye-opener would be an understatement; not only has it taught me many coding strategies (upon reading the entry for Introduce Null Object, I practically screamed “Why didn’t I think of that?”), but it’s also changed many of my ideas about the way I code.
Fowler’s clear, direct writing is one of the strengths of Refactoring; during the book, he introduces the concept of the code smell – a term which I had not heard before, and which perfectly describes a situation with which I have been familiar ever since I started coding – and points out that a sure-fire sign of smelly code is the presence of many comments; if you need to explain a method in excruciating detail, then you’re probably made it too complex.
One of the reasons why I adore Eclipse for Java development is because it shows JavaDoc attributes in its Intellisense methods – I don’t need to navigate through the Java API when I can hit Ctrl+Space and see the arguments the method takes. Without Eclipse, I have to either rely on memory (and in my opinion, life’s too short to memorize the proper sequence of arguments that a proper BufferedReader instance takes) or waste time navigating the API docs.
Objective-C, on the other hand, doesn’t have this – because method signatures can be broken up into multiple pieces. Take a look at these two method signatures – taken straight from the documentation:
- (NSRange)rangeOfString:(NSString *)subString options:(unsigned)mask range:(NSRange)aRange
versus:
public NSRange rangeOfString(String s, int i, NSRange nsrange)
When looking at the first method signature, I know that the second object will be an integer that controls the masking – simply by virtue of the fact that, like its parent Smalltalk, its methods recieve messages via keyword messaging. I can also make the assumption – and it is only an assumption – that - (NSRange)rangeOfString:(NSString *)subString options:(unsigned)mask and - (NSRange)rangeOfString:(NSString *)subString are valid methods. With the Java API, on the other hand, I have no clue what the second argument does; if I had to know, I’d need to be using Eclipse or XCode, not Emacs or Textmate. Textmate’s Cocoa completions don’t show any HeaderDoc information – but that doesn’t matter: the keywords tell me what the arguments are used for.
It seems to me that Objective-C’s syntax lends itself to clarity by its very nature; Java, on the other hand, depends on other programmers being clear and helpful in their method signatures. And as a short perusal through The Daily WTF reveals, programmers can be very, very unhelpful at times.
Cocoa Snippet: Finding a File’s ‘Kind’
+ (NSString *)humanReadableFileType:(NSString *)path
{
NSString *kind = nil;
NSURL *url = [NSURL fileURLWithPath:[path stringByExpandingTildeInPath]];
LSCopyKindStringForURL((CFURLRef)url, (CFStringRef *)&kind);
return kind ? [kind autorelease] : @""; // If kind happens to be an empty string, don't autorelease it
}
Thanks very much to Peter Hosey and David Phillip Oster for being unbelievably helpful in clearing up the labyrinth that is CSStringRef to NSString usage.
Right Tool, Right Job (?)
Any rational programmer will agree that different situations need different solutions. If you’re trying to write a blazing-fast and lightweight *nix process monitor, you wouldn’t use something like Ruby instead of C – Ruby’s just too slow. If you’re trying to write a tool which almost any programmer can easily read and modify, you wouldn’t use Haskell instead of Java – Haskell’s just too different. Up until now, I hadn’t even thought about the ‘right-tool-right-job’ tenet of my programming beliefs – but yesterday I did, and I came to a frightening conclusion.
Information technology is the fastest-changing industry in the world. New problems and solutions shift faster than you can blink; it’s almost every day I hear something about Nice or Groovy or some other language about which I know nothing. With such a massive shift of problems, solutions, and tools, it’s inevitable that some of them are going to be isolated.
Take XMLHttpRequest. (Please. *badum-shish*) Until Ajax came out, it was a solution in search of a problem; it’s pretty astounding to think that it took 6 years (XHR was developed by Microsoft in 2000) for such a brilliant solution to find a problem to solve. (The question of whether XHR is solving that problem, or if that problem actually exists, is another matter entirely.)
Now take Perl. Back in the early days of the web, it was indispensable – CGI was crucial for everything – and there was almost no other tool that could solve such a huge problem as how to glue together all the components that make up the Web. But now PHP, Python and Ruby have muscled in on the domain that Perl used to rule unchallenged; it seems as though this tool has lost its problem. If I were to develop a new, radical application Perl would be the last thing I’d choose – it’s old, not particularly fast, impossible to maintain, and Perl 6 is still far, far off.
What I’m saying is that if there’s one thing which your pet language does really well, then diversify. If, like Perl, you rest on your laurels, the problem you fix might be fixed by someone else. Sure, you should always pick the right tool for the right job – but if you’ve got a great tool which solves no problems, you’re never going to get anywhere.
I came to this conclusion after looking at REALBasic. Though I loathe everything that Basic and its progeny stand for (I have justification for this; I once had to write a 60-page tutorial on Microsoft Access & VB), I have to admit its crossplatform nature, especially for OS X widgets, is nearly flawless. After looking at the page, I mused on how much better Cocoa is than VB – and then it hit me like a lightning bolt:
What if REALBasic published something that could emulate the iLife window look?
How would new Mac programmers feel if they had to choose between a harder language /API that only works on Macs (yes, I know about GnuStep) and an easier language that works crossplatform and allows one to make slicker-looking applications than the other choice?
Yeah, there are fantastic third-party libraries that do the iLife thing for Cocoa, but what if REALBasic had it as part of the standard package?
Unless Apple gets their act together soon, their niche of native-looking, great application design might be invaded by REALBasic. Don’t let Cocoa fall into the trap of ‘good tool, no job’.
Blocks != Functional Programming
Joel Spolsky is one of my heroes. He has a vast amount of insightful articles that rank among the clearest and most relevant software writing today, and his blog gets more hits in a day than mine ever will. (Speaking of which, I hit 2000 visitors yesterday – around 10x more than I ever thought I’d get.) He’s a very smart cookie, and when he speaks, people listen. But last week, while browsing the top Reddit articles of all time, I was surprised to see his article Can Your Programming Language Do This? at #4. While it’s a good primer on Javascript abstraction, I don’t think it deserves as many points as it recieved. I spent the next few days thinking about why this article bothered me so much – Joel certainly didn’t say anything untrue, attack any favorite language of mine, or make some outlandish claim. But then I realized that Joel’s article fit together in a pattern of recent articles, all of which bothered me slightly.
Here’s what I realized: it seems that every language under the sun is being evangelized as an excellent functional programming language simply because it supports a few paradigms from FP.
Or, restated: Anonymous functions do not a functional language make.
The most egregious example of a pundit claiming a language is functional when it’s clearly not is Eric Kidd’s well-known Why Ruby is an acceptable Lisp. Kidd tells us explicitly that Ruby is a denser functional language than Lisp – and I’ll be the first one to admit that if I were to debate the “denser” part of that sentence, I wouldn’t know what I was talking about.
But Ruby is not functional – Wikipedia calls it a reflective, object-oriented programming language, and I agree with them. Yes, you can have block arguments to methods, continuations, generators, reflection, and metaprogramming – but it isn’t functional, for two reasons.
1. It’s hard to carry around functions as objects.
I really don’t know why Ruby hates parentheses so much – it’s probably part of its Perl heritage. In Ruby, you can call methods without sticking superfluous parentheses in there – take a look at this Python code:
" I'll write about Cocoa soon; disaster struck the app I was writing ".strip().lower().split()
Now take a look at the equivalent Ruby code:
" Apple's releasing a tool with XCode 3 which completely supersedes my Cocoa app - so I'm very depressed right now ".strip.downcase.split
Though you could put parentheses in front of strip, downcase, and split, Ruby will work just fine without them. Now, this feature makes for far fewer parentheses, thereby making code significantly more readable. But what if I want a previously-declared function as an argument? If I type in the name, Ruby will just evaluate the function. Sure, I could use the kludge that is Symbol.to_proc, but that’s ugly – and it wraps the function inside a Proc object, which has to be called with the call(*args) method. And that’s just ugly. In Python, all you need to do is type the function’s name to use it as an object, and append a pair of parentheses if you need to call it.
2. Variables are.
A purely functional language only has immutable variables. Ruby doesn’t. (Yes, I know LISP isn’t purely functional. But it adheres to so many other FP paradigms that we can overlook that.)
But I’m getting distracted, so I’ll cut the above point short.
Anyway, what I wanted to say was this – just because your pet language has support for anonymous functions/closures doesn’t make it a functional language. Sure, Python has lambda and list comprehensions (which are taken from Haskell, a purely functional language) – but it’s not functional, it’s object-oriented. Yes, Ruby has blocks (even if you do have to wrap them in Procs), but it’s not functional. Javascript may have support for anonymous functions, but its syntax can be traced back to Algol and the birth of imperative programming language. Hell, even Objective-C has blocks if you include the F-Script framework, and it’s the farthest thing from functional there is.
In conclusion, don’t say your language is a functional one just because you borrowed a few ideas from Lisp. If you want a real functional language, try OCaml, Haskell, ML, or Scheme. Calling imperative/OOP languages functional just makes the term meaningless.
How to Beat Rails
(Note: I really, really like Ruby and Rails. Anything disparaging that I say about either of them should be taken with several grains of salt. If I seem to be encouraging Pythoneers to crush Rails, it’s simply because I love both, and want all frameworks to be constantly innovating.)
Everybody with the slightest interest in web development has heard of Ruby on Rails. It thrust Ruby into the spotlight, created a hype machine that stubbornly refuses to go away, and made David Heinemeier Hansson a celebrity. I adore Rails – it’s by far the best web framework for simple CRUD apps. However, as a Python devotee, it hurt me deeply to see Ruby stealing the spotlight. As such, I embarked on a quest to find out why Rails is winning; I looked at Django, Turbogears, Pylons and web.py. After months of building the same simple CRUD app, I came to the following conclusion:
Python can beat Rails. It can grind it into a pulp in every way concievable – speed, elegance, coolness, extensiblity, organization, AJAXness, beauty, and flexibility. It can send Rails crying home to David while Guido basks in the Web 2.0 spotlight. But right now, Rails is thoroughly torching all of the Python web frameworks. The following is a list of recommendations that, if applied to a Python framework, could dislodge Rails from the position of King of the Web Frameworks.
Objective-C 2.0
As many of you know, Apple is releasing the new incarnation of Objective-C (creatively dubbed Objective-C 2.0) along with XCode 3.0 and Leopard. Though much of it is hidden under Apple’s elaborate nondisclosure agreements, people like Andy Matushack and Scott Stevenson as well as other sites have uncovered more information from the ObjC mailing lists and repositories than the meager scraps of info that Apple released on their website.
Since I can’t give any new information about this, I’ll just list my opinions, then brace for the reaming I’ll receive in the comments.
Disclaimer: Apple may change all of these things. This post is naught but conjecture heaped upon conjecture.