Heresy# Jul 17, 2009
I’m coming to the close of a contract, and I’m spending part time doing what is natural in that situation: looking for new contracts, and preparing some of my personal projects for release as products.
The first product scheduled for delivery is a Cocoa application for Mac OS X. Written in Scheme. This, of course, is heresy.
Cocoa applications are supposed to be written in Objective-C. (Well, to be fair, about a third of the code in this one is Objective-C. The rest I wrote in Scheme.)
Why would I do a crazy thing like that?
Well, I like Lisp. I’m a Lisp programmer for a reason, that reason being that Lisp is the clear winner on the Joy-Per-Unit-Effort scale. I’m not really partisan in my choice of Lisps. I like Scheme and Common Lisp and Clojure all just fine. If I ever had a favorite Lisp, it was Dylan, but it’s been a while since Dylan really felt like a Lisp.
So I was planning to release this product, and I was looking at options for packaging it. I could certainly have just written it in Objective-C. I’ve been using Objective-C and Apple’s development tools (and NeXT’s development tools before that) for nigh onto twenty years now. It’s a comfortable and familiar ecosystem. But I figured I’d give Lisp a fair shot before settling on the conventional choice because, you know, I like Lisp.
Clojure? Nah; the quick route there is a Swing app. The Java Virtual Machine has many advantages, but delivering great Cocoa apps is not one of them. Sure, if you spend enough time and effort, you can achieve a sort of approximation of a Cocoa UI, but it’s not a fun process. Less Joy Is Bad.
Common Lisp? Maybe. I love Clozure Common Lisp, and it has a great Cocoa interface. Only trouble is, universal apps are off the table, for now. Lispworks? Another good possibility. But my license is getting a little long in the tooth. If I was going to build a product on it, I should really get a new license, so I can have the latest version of Lispworks. And y’know? Right now, end of contract, looking for new contracts, and other revenue streams…not the best moment to spend a few thousand bucks speculatively. Prudence. One learns a modicum of prudence.
I was experimenting with a few options, and Gambit-C pleasantly surprised me. Gambit’s been installed on my systems since, oh, probably since 1990 at least. It’s a great Scheme system that has gotten steadily better over the years. But there’s not really any Cocoa support there, or so I thought. Go ahead, Google around; you won’t find much of anything. Except this:
It describes how this fellow set up remote debugging of a Gambit app running on the iPhone. Wait a second; Gambit on the iPhone? Um. Turns out I know something about iPhone apps. Turns out I’ve written some iPhone products (http://www.iphone4kids.net/2009/06/21/firstwords-learning-how-to-spell/). If this guy has Gambit code running on the iPhone, I’m pretty sure I can get Gambit code to run a Cocoa app, as well.
In fact, it was a piece of cake.
I had allocated a number of hours spread over a week or so to evaluate several alternative possibilities before just settling down and building the app in plain, vanilla Objective-C. After the first session with Gambit, I cancelled all the other evaluations and just wrote the app in Gambit.
It’s true that there is no Objective-C interface in Gambit. Turns out you don’t need one. Gambit has a very good foreign-function interface–a C interface really. Gambit compiles Scheme to C code, and then compiles the resulting C code to native code. One advantage of this approach, which is used by several other Schemes (Chicken and Bigloo, for example), is that you can embed C code in your Scheme, and pretty freely mix the two languages. In point of fact, because Gambit uses the platform’s C compiler, and because Apple’s C compiler handles Objective-C, you can in fact embed Objective-C code in your Scheme.
(define application-main (c-lambda () int <<c-code NSApplication *app = [NSApplication sharedApplication]; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [NSBundle loadNibNamed: [[[NSBundle mainBundle] infoDictionary] objectForKey: @"NSMainNibFile"] owner: app]; [app run]; [pool release]; ___result = 0; c-code ))
Well, whaddya know? We don’t need no steenking Cocoa interface.
So currently the app is about two thirds Scheme code and one third Objective-C code. It works great. It launches in under half a second and loads its test database in about seventy milliseconds. UI responsiveness and scrolling are as fast as you could possibly want. I built the UI the conventional way, in Interface Builder, and it calls into the Scheme code to display and edit data. The delivered app bundle is 7MB and it’s a universal application that runs on Tiger and later.
Turns out Gambit is a great platform for Cocoa app development. Who knew?