This is an interesting read about all this: ``Special Forms in Lisp'' by Kent Pitman (August, 1980)
Basically, function application is the “normal evaluation model”. You evaluate arguments left to right, and then feed them to the function the symbol refers too, doing so recursively so that function calls can be nested as both arguments or function symbol position.
I believe this is enough for lambda calculus, that means it is turning complete.
But certain things get very tricky very fast, especially loops, conditionals and symbolic computation.
In practice, nobody wants to encode loops, conditionals and all that in lambda calculus.
That’s why a practical programming language will want to add special forms, which we will first simply consider that a special form is any form that doesn’t follow function application normal evaluation semantics.
Those will let you do things more simply, such as conditionals, loops, local bindings, anonymous inline function definitions, symbolic computation, etc. They might even let you do things like evaluate things at compile time or at read time, and not always at function call time.
Now the question is, how do you implement those special forms? One way is to do so directly in the compiler, or directly in the runtime. These are going to be your “primitive special forms”, the language makes them available to you so that writing functions and writing your own custom “special forms” is already more convenient and practical.
Which primitive special forms the language makes available to you is up to each Lisp to decide. I think the only required special form is lambda, or a form that defines a lambda, and then you can technically as a user use that language to implement anything through lambda calculus, but no one would want to use such a language.
Anyways, so Clojure decided on a set of primitive special forms that the reader and compiler and runtime implement directly, that means the symbols aren’t resolved to anything defined in the language itself, they are special symbols that the reader, compiler and runtime look for directly.
Nowadays, people call macros such special forms that the user can implement using primitive special forms + normal functions offered by the language itself. But as my link shows you, there exist other mechanisms in some Lisps that can also provide the user ways to add custom special forms.
Anyways, in Clojure special forms tend to refer to primitive special forms, it seems let
is an exception as it is a macro but also tagged as a special form in metadata, not sure why.
Bottom line, special forms are there to make using the language in its “bare form” more practical and convenient. It can also help a compiler, especially one that doesn’t actually compile to lambda calculus and then runs using a lambda calculus resolver machine. Translating from raw lambda calculus to a turing machine with x86 instruction set isn’t that simple, not sure it’s even doable. So it becomes much easier to have an actual conditional form that can be compiled to a conditional instruction set in the assembly, then to have to use lambda encoding and resolving for it.
That’s an important point, because I think some of Clojure’s special forms were chosen to make it easy or possible to compile to JVM bytecode and support interop and other such things.
Hope that helped.