2007-11-29

Choosing a Programming Language

This article was originally written for iDevGames and CreateMacGames (no longer in existence), but I'm republishing it here as a complement to my "Why not C++" post. It's not exactly what I'd say today (Haskell's a lot harder to master than I make out, and a lot better at I/O than I make out) but I think it's still a good read.

Like Moths to a Naked Flame

An oft-asked question by beginner programmers is, "What language should I use?". The answers that are given vary, but usually involve heavy recommendations of C and C++. And that's where it stops. The student learns the recommended language, and never stops to consider what else they might learn.

Time passes, the bright-eyed student becomes something of a guru herself, and when it comes their turn to answer the inevitable question for the next generation, she's hemmed in by her own experience. "C or C++", comes the stern reply, and another generation marches blinkered toward the same fate.

Why must this always be the case? Why is it so difficult to leave the beaten track? Why, when it comes time to start a new project, is the question always "How can this be done in C++?", rather than "What language would make this project easier?"?

Why Computers are Better than People, Reason #457

The point many people miss, here, is that computer languages aren't like human languages. It might take you a lifetime to master Japanese. You're looking at maybe a week for Python. One week to mastery. Not "good morning" and "my name is Ellie" clumsy beginnings; not even mere competent conversation; we're talking complete, total and utter mastery.

Pick a difficult language, say Perl, and we might be in two-week territory.

How about a completely different paradigm; a whole new way of thinking? It shouldn't take a month to master Haskell.

There's no reason to limit yourself by the languages you know — the effort to acquire a new one is miniscule, while the pay-offs can be tremendous.

On Round Pegs and Square Holes

Every new project gives the opportunity to start afresh; to reevaluate preconceptions; to apply knowledge gained during the execution of the last. The choice of a programming language is no different.

The decisions made now will have a profound impact on the outcome of this latest venture. The choice of language(s) will affect how easy it is to produce the program in the first place; it will affect how easy it is to modify the program later; it may even affect how easy it is for the program's users to customize your program to their liking. Why ignore the choice given you?

People don't like to ask the difficult questions. The questions like "Why are we using C (a language for writing high-performance code) to create a desktop office suite (where performance requirements are very low)?". The questions like "Why are we using Java (a language for writing high-level reusable code with low performance requirements) to write a software 3D renderer supposed to run in real-time?". The questions like "Why are we using Haskell (a language good for complex mathematical computation, but poor at I/O) to write a file-format translator?". The questions like "Why are we using Perl (a language good for text-processing) to write an image-editing program?".

Would you fire your builder if she tried to drill a hole in your wall with a spanner?

Choosing the Right Tool for the Job

Every language has its strengths and weaknesses; it has areas where it particularly shines, and areas where you'll only be fighting the language to try and accomplish anything.

When you start your next project, stop and think for a while. List the requirements you have for your programming language, and match them up against actual languages. Chances are you won't find an exact match, but this should help focus your attention on a small group of languages that are more suited to your task than the others.

Color-Coordination; or When a Track-Suit isn't Enough

One problem you've likely encountered having followed the advice of the previous paragraph is that different parts of your program have different requirements. If you're writing a game, for example, you'll probably have a large portion of your program (containing AI, game logic, user interface, &c.) which needs to be easy to build and easy to change, but where performance isn't critical. Unfortunately, you'll also have a small, hard core of the functionality (probably graphics and physics routines) where performance is of the utmost importance.

As with clothing, not everything has to be the same color. As with food, you're allowed to have more than one flavor in a meal. In programming, you don't have to use the same language throughout the entire program. There are ways and means of embedding almost all languages in other languages, even if it involves going via C on the way. This way you can enjoy the benefits of each language in the areas suited to it, without succumbing to its weak areas.

Beware the fashion police, however — too many different languages in one project will quickly tie you up in the mess of integration, rather than letting you get on with using your chosen languages. I still have visions of my high-school English teacher, whose favorite color combination was yellow, lime green, turquoise and hot pink. You don't have to be like her!

Forging a New Path

Take off your blinkers! Look around you! Marvel at the vast array of possibilities! Dare to find the language which makes your task easier!

And when you, in turn, get asked "Which language should I learn?", do the right thing — ask a question in return: "What do you want to create today?".

I Broke GCC

CookieJar:Desktop keith$ cat test.mm 
template <typename S>
void swap_struct(S *s)
{
    const char *encoding = @encode(S);
    // ...
}
CookieJar:Desktop keith$ gcc test.mm 
test.mm: In function ‘void swap_struct(S*)’:
test.mm:4: internal compiler error: Bus error
Please submit a full bug report,
with preprocessed source if appropriate.
See <URL:http://developer.apple.com/bugreporter> for instructions.

Darn. That would've been a cool piece of code. Oh well, a macro will suffice.

Radar 5619052.

Why not C++?

This was originally posted on iDevGames, but since people are constantly asking me why I hate C++ so much, I thought it should be posted here too.

My philosophy is that at any given time, there are three things that you may care about when programming. From least common to most common:

  1. You want to write fast code.
  2. You want to write correct code.
  3. You want it to be easy to write code.

Unfortunately, these tend to work against each other.

If you want to write fast code, you need a language which takes you close to the hardware. It needs to provide a fairly direct mapping between the "high-level" source and the assembly, so that you can pinpoint problems in the assembly and know exactly what needs to change in the high-level code to fix them. The only language I've ever used which does this is C. With C++, as soon as you get into references, copy constructors, operator overloading, etc. there is too much going on for that direct correlation between source and assembly to exist.

If you want to write correct code, you need a language which prevents you from making as many errors as possible. That implies a strict static type system, array bounds checking, an inability to poke around directly at bits and bytes in memory, etc. Obviously, C++ doesn't fit that bill; the closest language I've found is Haskell. Java's unchecked exceptions rule it out, though there are tools that can help with that.

If you want it to be easy to write code, you need a language that allows you to express what you want; that doesn't tie you down to rigid typing structures, or keep you fiddling with minutiae like memory management. This tends to mean a highly dynamic object-oriented language. Unsurprisingly, since this is the most common category for code to be in, this is the category with the most appropriate languages in it. Ruby and Python are two obvious ones. C++ is far too static, and keeps you far too immersed in low-level details to be useful here.

Of course, any given project is likely to consist of parts which fall into each of these three categories, which leads many people to seek a "compromise language" — one which is "fast enough", "safe enough" and "easy enough". Ultimately, I believe such languages are always a waste of time. You will spend more time optimizing the bits that need optimizing, more time fixing bugs in the critical sections of the code, and much, much more time trying to design your software in such a way that you can actually build it, than if you were using the appropriate language for each part.

These languages are not difficult to tie together. All have easy ways to interact with C, which can be used as a thin layer of glue to stick them all together. The boundaries between the sections of code with different requirements tend to be reasonably well-defined, and you can therefore avoid crossing your language boundaries too often.

If you're using the right language for the job, your code will be as clear, concise, and well-designed as it's possible for it to be. To use the favorite vehicle analogy, you wouldn't try to use a Ford Ka as a personnel carrier in a war zone, and you wouldn't drive to the shops to get the groceries in an APC. Either might "work", but you'd be better off with two separate vehicles.

And please, don't try the argument that "it's easy for me to write C++" or "I don't waste time fighting C++'s memory management and lack of dynamicity" or "I don't make bugs these days"; it's well known that people are notoriously bad at analyzing their own productivity. There are simple studies and metrics that prove that programmers are more productive in more dynamic languages, and write safer code in safer languages.

2007-11-26

GCC Predefined Macros

As a follow-up to my earlier post on macros for detecting compilers, OSes and hardware, here's an easy way to find all the macros GCC is predefining for you:

echo | gcc -dM -E -x objective-c -arch ppc -mmacosx-version-min=10.3 -

Obviously, substitute the appropriate language, architectures, and minimum Mac OS X version. The languages are c, objective-c, c++ and objective-c++. The architectures (as of Leopard) are ppc, i386, ppc64 and x86_64.

2007-11-25

5MB of logged iChats = ...

I was looking for a URL, and all I knew was that it was probably sent to me by a particular person on iChat. I found the iChat logs, found the chats with this person (126 documents, about 5MB on disk) and opened 'em all up.

Bad idea.

When, after about ten minutes, iChat was using 400MB of RPRVT memory and was still not responsive, I force quit.

Bad idea.

Turns out iChat (on Leopard only?) saves the state of all its documents, and reopens them when it relaunches. I reopened iChat, and it became instantly unresponsive, doing exactly the same thing. So, I left it to finish opening all the documents, then option-clicked a close box. It closed all the windows quite happily, but has been unresponsive since. If I force quit now, will it reopen all those documents next time I open it?

As if Quesa wasn't a bad enough idea...

Please kill me now.

2007-11-23

Re: Apple and Gaming: being constructive

In response to a rapidly-derailed thread on the mac-games-dev list. Everything I was going to say has been said already, but I still feel the need to vent!

What can Apple do to help Mac game developers? They can carry on doing exactly what they are.

The Mac is a computer for users. It always has been, and hopefully it always will be. That means that in any situation, the user comes first. It means that developers need to abandon their arrogant Windows ways when they come to the platform, put the users first, and write games that play nicely with the rest of the system.

A game is just another application. It doesn't get special treatment. It should run off a disk image, install with a drag and drop, store its hidden files in ~/Library/, use the system's concept of user accounts rather than hacking on its own, use as little battery power as possible on laptops, leave the user's windows where they are, and so forth. These are all non-negotiable parts of being a Macintosh application, and there is absolutely no need for a game to violate them. If you think you have a good reason why these won't work for you, you don't. Think again, there's a better solution.

If Apple were to ship a gaming SDK, it would give the false impression that games are somehow special, deserving of extra attention, subject to special exceptions. They're not.

Games can perform self-update in the same way that Sparkle-enabled applications already do. Games can download content automatically to a location in ~/Library/. If the game wants to enable a competitive high-score table between user accounts, it can ask for administrator permission to create that table in /Library/Application Support. These are things which are well-known to application programmers, and don't occasion complaint. Why do game developers think they're special?

LSMinimumSystemVersion

I tried using the Info.plist key LSMinimumSystemVersion to require 10.4.11. It doesn't work — even on 10.4.11, you're told you can't do that:

If I change the app to require 10.4.8 instead it works fine. I'm guessing that LaunchServices is using gestaltSystemVersion to get the system version, which returns 0x1049 for all of 10.4.9, 10.4.10 and 10.4.11... Perhaps Apple should follow their own advice about retrieving the system version...

2007-11-22

Universal Binaries from Autotool'd Projects

A frequent question I get is "how do I build a universal binary from an autotool'd project?". Unfortunately, as with all things autotools, the answer isn't quite straightforward. The best approach is to configure and make the project multiple times, once for each architecture you want in your UB, with different compiler settings each time.

Since autotool'd projects vary widely, the details for each will differ, but at the least, you'll need to set the CFLAGS environment variable appropriately (eg. -arch ppc -isysroot /Developer/SDKs/MacOSX10.3.9.sdk -mmacosx-version-min=10.3), and pass the correct host for a cross-compile to configure (eg. .configure --prefix=`pwd`/build/ppc --host=powerpc-apple-darwin7).

Once you've got a version for each architecture built, you can use lipo to join them together into a UB.

If all that sounds like too much effort, my "Third-Party" project does it already for a few common open-source libraries, and the infrastructure there makes it easy to add more.

More Kiloplane Bugs

Finally, 10.5 comes out, and at last the GLSL rendering bugs that have plagued Kiloplane seem to be fixed across the board. Celebrations all round.

Today, Ellie got a new MacBook (the first Mac with an Intel GMA X3100), and I got to test Kiloplane there. Guess what? Modes 3 and 4 render incorrectly. Sigh.

2007-11-20

Info.plist Keys

Apple has a list of Info.plist keys. There's a bunch there that I didn't know about that look useful, in particular ATSApplicationFontsPath and LSMinimumSystemVersion(ByArchitecture).

CoreVideo and QTCapture Timings

So, your CVDisplayLink has an "output time", and the frames you get from your QTCapture connection have a "presentation time", and the numbers look awfully similar, give or take a small offset. You might be forgiven for thinking that both sets of timings share the same timebase.

They don't. The clocks skew relative to each other, and quite fast, too (two or three seconds over ten minutes or so).

You have been warned.

2007-11-19

GHC Rules for SCons

Get them from my Subversion repository.

I don't claim to be a SCons or GHC master, but these are working well enough for me, and might prove useful to somebody else.

To use them, you need to add the appropriate things to your tools and toolpath, eg.

    tools = [ 'default', 'ghc' ],
    toolpath = [ 'SConsHs' ],

You probably also want to replace the linker with GHC, eg.

    LINK = '$_GHCCOM'

You can add flags to be passed to GHC to HSFLAGS:

    HSFLAGS = [ '-O2' ],

There's also HSCFLAGS, HSAFLAGS and HSLFLAGS for passing options to the native compiler, assembler, and linker. HSPATH is the GHC equivalent of CPPPATH.

Once you've done all that, you can just add .hs files to your list of source files.

The tool tries to generate correct dependencies for auto-generated FFI files.