Syntax & Power / Objective-C in Julia
tl;dr: I’m making an Objective-C bridge for Julia. To make a short story long, I’m going to show you it, then tell you some things I think.
An interesting topic for me is this idea of the “power” of a language. What features, or combination of features, make a language powerful? What are the limits of expressiveness, and how can we test them? And above all, what’s the most concise and direct way to express my current problem? (You might apply this to natural languages just as well as programming ones.)
Many approach this problem with an analytical deconstruction, but I’m no bearded theorist and tend towards a good experiment (don’t tell me why, show me why). I like to explore expressiveness by pushing languages, and seeing what kinds of problems they express well. And a great stress test for a language’s expressiveness, in my opinion, is trying to embed a different language inside of it.
Of course, this is always technically possible — in the limiting case, by writing an interpreter and embedding code as a string. So I’m much less interested in what’s possible as opposed to what feels convenient, idiomatic, and pragmatic.
With that in mind, I’d like to show you what happened when I applied this to Julia, and tried to embed Objective-C so that I could interactively build Cocoa apps. For reference, Julia looks like this:
function mandel(z)
c = z
maxiter = 80
for n = 1:maxiter
if abs(z) > 2
return n-1
end
z = z^2 + c
end
return maxiter
end
Objective-C looks like this:
[someObject doSomething:arg1 withSomethingElse:arg2];
Objective-C’s methods are pretty idiosyncratic, and don’t look much like Julia’s (or anything else’s); they usually have a long name interspersed with their arguments. This makes it difficult to embed nicely; for example, here’s what this same call embedded in Python looks like:
someObject.doSomething_withSomethingElse_(arg1, arg2)
Here, on the other hand, is what that method call looks like in Julia:
@objc [someObject doSomething:arg1 withSomethingElse:arg2]
The @objc macro call (which you can think of as a kind of annotation) enables the square bracket syntax, but doesn’t need to be around every call. For example, we can translate this Objective-C:
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Hello from Cocoa"];
[alert runModal];
[alert release];
into this Julia:
using ObjectiveC, Cocoa
@objc begin
alert = [[NSAlert alloc] init]
[alert setMessageText:"Hello from Cocoa"]
[alert runModal]
[alert release]
end
What’s really nice about this is that we can freely mix Objective-C objects and method calls with Julia’s (dynamically typed) variables and functions. It’s also really easy to define classes with methods that call back into Julia, which is really useful for setting up event listeners:
@class type Foo
@- (Cdouble) multiply:(Cdouble)x by:(Cdouble)y begin
x*y
end
end
@objc [[Foo new] multiply:5 by:3]
This of course returns the expected result of 15, and you can even redefine the method on the fly.
In a nutshell, you can bend Julia’s syntax and semantics pretty far if you want to. Now, I’m not going to argue that this automatically makes Julia the most expressive language ever, or anything like that; it’s a microbenchmark for expressiveness and has the same problems as microbenchmarks for performance. Nevertheless, it’s clear that Julia passes the test with flying colours, and I think the language raises some interesting points about syntax and power in general.
We can take lisp, held as one of the most powerful languages around, as a co-example. Could we get the same level of integration with Objective-C there? The answer, in most lisps, is “technically, yes” — but it’s somewhat unidiomatic, going against the Lisp philosophy of simple homogeneity over specialised syntax. In general, embedding other languages in lisp tends to make them look a lot like lisp, regardless of what’s technically possible.
To see why we must understand why lisp eschews syntax, and how it relates to power. The problem with specialisation, in general, is that it discourages abstraction. For example, Java, C and the like make pointer operations and machine arithmetic readily available via constructs like
xs[i] = a*b;
But as soon as you try to use a higher-level data structure, you have to write:
vector_setindex(xs, i, matrix_multiply(a, b));
It’s easy to dismiss the verbosity as merely an issue of syntax sugar — the two statements are logically equivalent, after all. Then again, if humans were perfectly logical we wouldn’t need to program in the first place. In practice the overhead here is going to encourage sticking to convenient, low-level constructs.
The lisp solution to this is simple, but beautifully effective — if everything is homogeneous, there are no syntactic special cases and abstractions have no overhead.
That’s all well and good, but I say there’s another way. Provide useful syntax, and make sure it’s overridable via powerful operator overloading and (as a last resort) macros. You keep the syntax and lose the special cases to get the best of both worlds.
This enables something interesting: However powerful it is to be able to express a concept in your own language, it’s a often a lot more powerful to express a concept in its own language. That’s true whether you’re multiplying two matrices with A*B or calling Objective-C functions with Objective-C syntax. In many cases you can transcribe directly from the textbook, the paper, the documentation, the specification — which gives you the freedom to think in terms of the right abstractions and let the language get out of the way. Few languages have explored this much, but Julia does it really well.
Again, I’m not necessarily arguing better or worse here, for any language. All languages are DSLs to an extent; C for systems, Go for networking, Python for scripting, whatever. There is no truly general purpose language, and while those like Julia and Lisp may well be more powerful in some sense, no one of them eclipses the other’s usefulness. But I hope people will continue to push the boundaries on linguistic expressiveness nevertheless, and come up with languages which are able to express ever more concepts with ever less friction — I for one am excited to see what’s next.