Applicable symbols

Here are my notes about being puzzled about some Clojure code and diving into the implementation to figure it out. Although I figured it out the hard way, the exploration turned out to be interesting for me.

As I was reading Geoff Shannon’s blog post, I was thinking, “Oh, maybe this will turn out to be a mixup between compile time and run time”. (At least, in my experience writing macros, it can be challenging to keep that straight.)

Instead, what confused me was line 103 in the last example (here I’ve changed the symbol from '+ to 'x):

1

(apply 'x[12]);=> 2

Huh? I don’t understand. Why does this return 2, instead of raising an error?

First, let’s factor out apply and confirm the same result for a direct application:

To summarize so far: It appears that it is not an error to use a symbol in the function position of an application if the arity is 1 or 2.

I’m aware that Clojure collections are applicable. And I’m aware that Clojure tends to nil pun. So the arity 1 case might be less surprising… except that neither 'x nor 1 is a collection, so this seems like it should raise an exception, not evaluate to nil. And I don’t understand the arity 2 case.1

What to do? I could ask for help. But let’s take this as an opportunity to go spelunking in some Clojure implementation code I’ve never seen before.

From looking at the source for clojure.lang.AFn, it seems that all invoke members call throwArity. It’s been some years since I did C++, and I’m not up-to-speed on Java. But if I understand correctly, AFn is a sort of default class that always errors. Applicable things (functions, keywords, collections) probably derive from AFn and override invoke members to do something other than error. Somehow such a class is created for the symbol 'x, and I’m guessing that it overrides the arity 1 and 2 variants of invoke.

That seems plausible. So, how to find this in the Clojure source? Instead of searching the code on GitHub, let’s git clone the source locally and use Emacs rgrep to search for invoke. I’m sure there are Java source navigation tools, but probably not worth choosing/installing/learning for something this straightforward. Yes rgrep returns 1200+ matches, but flipping through them quickly there are some obvious patterns — huge swaths that seem OK to ignore, and a few names that pop out.

For example in APersistentMap.java I see arity 1 and 2 definitions of invoke:

So this seems to explain why arities 1 and 2 are special. Next question: What is RT.get? In C++ this would mean a static get member of an RT class. That seems to be the case here, looking in RT.java. get comes in both arity 1 and 2 flavors (not counting the first argument; this is a static member so the implicit this argument is explicit). First here’s arity 1:

Aha. ('x 1 2) is being treated as, “Look up 1 in 'x and if not found return 2”.

Editorializing

It makes sense to support a symbol in the function position of an application to enable doing this:

1
2
3

('key{'key42});=> 42('key{'key42}42);=> 42('key{}42);=> 42

Of course. That’s awesome.

But when the second element is not a collection? It should error. Looking up a key in an integer doesn’t make any sense. It is the result of a mistake. Clojure should tell you it’s an error. Returning something here is as bad as silent coercions in Javascript.