Friday, January 08, 2010

Cocoa vs Haskell

So here's the deal - if you want to write a Mac GUI app, you want to use the Cocoa framework. As a practical system for writing desktop GUI apps, the Cocoa stack simply has no equal anywhere. Part of what makes Cocoa so great is the Objective-C language, which adds a very flexible and dynamic OOP system on top of standard C.

Objective-C turned out to be a great match for creating desktop GUIs. It's obviously a general-purpose language, but for general programming it would be no where near my first choice. My first choice right now would be Haskell, even though I'm still a beginner and I pretty much suck at it. But I get why Haskell is so good (and so much fun).

So if I want to write a Mac GUI app, can I use Haskell? I've had a play with HOC which is a very neat bit of software that bridges Objective-C to Haskell. You can call Haskell from Objective-C and implement Objective-C classes in Haskell. There are some challenges trying to use HOC—it's not exactly straightforward to get it up and running and integrated into your XCode project; and it's under active, but slow, development and doesn't have a stable release. But at a more fundamental level I've come to the conclusion that, while it's very cool, HOC probably isn't worth it.

If you can get HOC working, you'll have a full-blown bridge that lets you use Cocoa APIs from Haskell. But is that actually worthwhile? The things that Haskell excels at are basically useless when using Cocoa, which is completely based around stateful objects being mutated by events and messages. While you could have all your code—including the Cocoa GUI stuff—in Haskell, the GUI code will all be imperative using the IO monad. All the effort just gets you Objective-C with nicer syntax.

So for Mac apps, I think sticking with Objective-C for the GUI code is the best choice. As a language, it sucks compared to Haskell, but it just works very well with the Cocoa APIs. But what about using Haskell to implement the Model and do other computational heavy-lifting under the hood? This turns out to be much simpler than a full Objective-C bridge. GHC of course offers a standardised foreign function interface to C out of the box, and Objective-C is a superset of C. So you can call Haskell from Objective-C code just as you would from plain C, and it works quite naturally.

But it's a bit trickier to build a complete Cocoa application that does this. There is a HaskellWiki page that describes one approach to getting a complete XCode project set up. I tried it out and it works, although things don't integrate very well. You have to compile the Haskell code using ghc outside of the XCode build process, and you have to manually track down all the libWhatever.a files that your Haskell code needs to link against. I've made a simple demonstration project for XCode 3.2 that works for me on OS 10.6: http://github.com/quelgar/HaskellCocoaDemo. This is basically the classic Currency Converter app from Apple's Cocoa tutorial, but it does the currency calculation in Haskell. Here's the rough list of steps I performed, starting from the standard XCode template for a Cocoa Application:
  • In the project build settings:

    • Set the architecture to just "i386". GHC is today only built for 32-bit Intel on Mac
    • Add "-liconv" to "Other Linker Flags". GHC libs need it.
    • Add your GHC include directory to "Header Search Paths". For GHC 6.12.1 on my system, this was "/Library/Frameworks/GHC.framework/Versions/Current/usr/lib/ghc-6.12.1/include/".
    • Add "/Library/Frameworks/GHC.framework/Versions/Current/" to the "Library Search Paths" and mark it recursive. Not sure if this is actually necessary since we end up adding all the libraries to the project manually anyway.

  • The Haskell function that will be called from C is in HaskellConvert.hs, which uses the typical FFI stuff. You compile this with GHC like so: "ghc -c HaskellConvert.hs". This outputs various files, of which the following have to be added to your XCode project:

    • HaskellConvert.o
    • HaskellConvert_stub.o

  • You now have to add all the lib*.a files from GHC that your Haskell code needs to link successfully. The wiki page I linked to above describes one way of doing this. It's not pretty, but it works. Once you know you need a particular .a file, just drag it from the finder to the project, and accept all the defaults in the add file dialog. It will be added to the link stage automatically.
  • Modify main.m to perform the Haskell runtime setup and teardown.
  • In this demo, Haskell is called from Converter.m, so this file needs #import "HaskellConvert_stub", and then just make the call to Haskell.
Probably the biggest downside of this plain C approach vs HOC is that you can't pass Objective-C objects to Haskell (well, you can, but they are not easy to use from Haskell, to say the least). So you have to convert whatever Objective-C data structures you're using in the GUI to plain C types for interfacing with your Haskell APIs. Haskell wrappers for the OS X C APIs for CoreFoundation and the Objective-C runtime might make using Objective-C objects from Haskell feasible without a full language bridge, but I'm not sure about this.

The GHC installer for Mac installs the bulk of GHC under /Library/Frameworks/GHC.framework, but I don't think it actually works as a Mac framework. I could be doing something wrong, but adding it to XCode as a framework didn't work (XCode just said it wasn't found when I tried to build, from memory). It would be great if GHC could be made a fully-functional Mac framework, as we would no longer need to manually set include and lib search paths, or explicitly add every Haskell .a file to the project. But to make this useful, I think we'd need some automated way of building any Cabal package as a framework. I'm not sure if that's feasible.

3 comments:

(sqrt(geek))3 said...

I agree that integrating Cocoa and Haskell would be a complete waste of time. For one, how many application *really* need a native gui? I'd rather just make a browser-based interface for any Haskell application. Realistically, development is going further in the direction of the browser, anyway, and this approach solves a ton of problems. Develop one Haskell web app and with different stylesheets, you can support user access via standard as well as mobile web browsers. For most cases, I think that a browser based interface is sufficient. Only if you really need access to hardware would you need to figure something else out.

Adam said...

I've been trying to get a GUI running with Haskell/Mac OS X as well and eventually settled for ditching Cocoa and using gtk2hs and gtkquartz. I'm not sure if I'd be up to the task of actually deploying the program as an .app, which seems like it could be a big burden.

Still, I'm using Linux as my main OS, so having programs just work in OS X was an important criteria.

Robert Lorentz said...

I agree with your approach and commentary 100% and am doing exactly the same thing.