Why LISPs have no auto-currying


To understand this article you need to know

  1. Whas is Currying
  2. What is Variadic function

Why Clojure was created without auto-currying?

This thought was passively floating in my head until suddenly I've got striked with an answer. I love ideas behind LISP but it's also clear for me that currying brings more benefits than default arguments and variadic functions. Composability can't be overrated.

Currying and Variadic functions can be viewed as mutually exclusive. While nothing prevents a language to have both types of functions every language tend to gravitate towards one or another pole. Let's say both types are not perfectly compatible in the interface layer.

Currying is effective when all (or most of) functions are curried. So this design decicion is crucial and irreversible. And it were chosen to built Clojure with variadic functions.

As always, let's see in StackOverflow.

One of the proposed answers is "to make it Java compatible". I would believe that but Common LISP, Scheme and Racket also have no auto-currying. So compatibility may be one of the reasons, but I'm not satisfied yet.

Another (implied) answer is "because auto-currying can be confusing in dynamically typed language". That's a strong argument. Haskell wouldn't even allow you to mess things up but in Clojure or JS you can easily get lost in lambdas.

My favorite, design argument, noone mentioned, is "because of parens". LISP users adore phrases like "Parens make no difference" and while I'm kinda agree with what they mean, the truth was opposite in this case.

We need to go down to the basic syntax level. Arithmetic operations. I'm going to provide examples in Haskell and Clojure but all said will be valid for corresponding language families as well.

In both Haskell and Clojure + is a function. Haskell supports infix syntax: 1 + 2 which is a syntactic sugar for (+) 1 2. Clojure supports only prefix calls: (+ 1 2).

Now how to add three numbers? Haskell version is 1 + 2 + 3 which desugars to (+) ((+) 1 2) 3. Clojure version is (+ 1 2 3).

Clojure obviously has no syntactic sugar and is nice-looking because of variadic + function. If + would be an unary function like in Haskell, that would yield a disturbing:

((+ ((+ 1) 2)) 3)
((+ ((+ 1) 2)) ((+ 3) 4)))

Whew! It happens to be less readable than a lambda calculus! Parens seemingly do not perform as well as space in the role of function applicator...

We could reinvent a sum function:

(sum [1 2 3])

or utilize reduce

(reduce + [1 2 3])

But they still require four characters instead of one.

So the current variadic approach gives us the cleanest view of arithmetic formulas possible to LISP. Same reasoning can be applied to logical operators etc.

Macros to unfold human math syntax? They are not composable to begin with. You can't just clj (map my-macro [1 2 3]). You need to add a lambda wrapper at least. A complete overkill for hello-world stuff...

Macros to create immediately curried function? Well, you'll need to reimplement a great chunk of Clojure standard library to make things "curry-friendly". Some core Clojure functions aslo have reversed argument order (i.e. data-first). I doubt you have so much time to spend.

As already stated, currying is beneficial at a large scale. Not as a micro data-flow optimization. Trying to fix that we're immediately starting to lose the simplicity which made LISP stand out.

So variadic API choice for LISP seems to be predetermined by a LISP syntax itself right from the beginning. Not a good thing to discover.

Author: @ivankleshnin