2

I discovered that a lot of "special forms" are just macros that use their asterisks version in the background (fn*, let* and all the others).

In case of fn, for example, it adds the destructuring capability into the mix which fn* alone does not provide. I tried to find some detailed documentation of what fn* can and can't do on its own, but I was not so lucky.

It definitely supports:

  • the &/catchall indicator

    (fn* [x & rest] (do-smth-here...))
    
  • and curiously also the overloading on arity, as in:

    (fn* ([x] (smth-with-one-arg ...) 
         ([x y] (smth-with-two-args ...))
    

So my question finally is, why not only define:

(fn& [& all-args] ...)

which would be absolutely minimal and could provide all the arity selection via macros (checking size of parameter list, if/case statement to direct code path, bind first few parameters to desired symbol, etc..).

Is this for performance reasons? Maybe someone even has a link to the actual standard definition of the asterisks special forms handy.

amalloy
  • 89,153
  • 8
  • 140
  • 205
Lazarus535
  • 1,158
  • 1
  • 8
  • 23
  • Did you mean "(fn* ..." above and not "(fn& ...") ? – Alan Thompson Sep 15 '15 at 22:30
  • The fn& was changed by an editor from my originally written fn* for clarity reasons. The fn& in this case represents a pretty minimal version of fn*. This version however does not exist and is just a hypothetical construct. – Lazarus535 Sep 16 '15 at 10:54

3 Answers3

5

Arity selection leverages the JVM virtual method dispatch: each arity (from 0 to 20 arguments) has its own method and there's a single method for 21+-arg arities.

You may notice the applyTo method which is the generic method akin to what you propose with fn&. It's implementation is just a giant switch to select the correct specialized method.

cgrand
  • 7,939
  • 28
  • 32
3

Yes, you could do all that as macros on top of the ultra-primitive fn& that you propose, and it would certainly simplify the compiler implementation. The reason this is not done is partly for performance reasons (it would be rather slow, and the JVM already has a fast facility for dispatching based on arity), and partly "cosmetic": it means that each arity of a function is a different method of the JVM class that a function is compiled down to, which makes stacktraces nicer. This also helps the JIT "understand" our functions better, so that it can optimize accordingly.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 1
    I think that this are two very good reasons. Clojure is using/adapting to the underlying JVM architecture to its favour in other areas as well. So it is probably a way of communicating useful information to the compiler/JVM which would otherwise be lost via macros. – Lazarus535 Sep 15 '15 at 22:47
  • Accepted because it provides the best overview of why fn* is the way it is. – Lazarus535 Sep 16 '15 at 10:56
0

My guess would be that this is convenience/extensibility driven. The compiler (where fn* is actually "defined"/processed) is written in java and handles the minimum needed functionality in order to bootstrap the language while fn is a macro that builds on top of it. Same with some of the other forms. Somewhere there was a statement from Rich that he could rewrite the compiler from java to clojure but does not see the benefit (correct me if wrong).

gdanov
  • 312
  • 2
  • 12
  • `fn&` would be much simpler to write than `fn*` is, and the macros to build `fn*` out of `fn&` are not very difficult. The way things were actually done is certainly less convenient than if Rich had taken the simple `fn&` approach. – amalloy Sep 15 '15 at 22:33
  • My thinking is that this current implementation is against convenience and extensibility, because just applying all the arguments to a function and handing them over in a vector/list (as in & rest) for the simplest case (fn*) and let a macro deal with the individual bindings is easier than directly binding some arguments (before the & rest) to "single" variables via a special form. Also the special form (fn*) must now handle the arity resolution itself, which could (in my "suggestion") also be a library feature (provided by fn). – Lazarus535 Sep 15 '15 at 22:38