We’re in agreement that a form is an expression. A form will have evaluation rules based on what it is.
A symbol is generally bound to something else, e.g. a value or a function, so evaluating just the symbol in any context will result in what it is bound to.
Keywords, booleans, numbers, strings evaluate to themselves. Vectors and hashmaps also evaluate to themselves.
Now if we try to evaluate a list, the first item is assumed to be a callable, hence its value is looked up and that is used to call the rest of the arguments. If the callable is an ordinary function, the remaining arguments are evaluated before being passed to the callable. If the callable is a macro, the arguments are passed in exactly as they are without being evaluated, and something is then returned by the macro, usually new code.
Now there is a small handful of “special” symbols, such as def
, if
, loop
, quote
and so on which do not fit this evaluation model, hence they are called special forms. My observation was that calling them “special forms” can be confusing, especially to beginners. The main things to be aware of about these special forms is that each special form has its own custom evaluation model with respect to the arguments you pass into them. You cannot write these using ordinary functions, as their functionality happens during compile time, whereas functions evaluate at runtime.
The way I see it, macros allow you to write custom functionality which is a lot closer to special forms than to functions.
In terms of evaluating the symbols which refer to special forms, they can only be evaluated as the first item of an s-expression, and nowhere else. Which is exactly the same case with macros.
Not quite, I said you could rebind a special form symbol within a let binding or a function binding, and you then pointed out you could also do this at a global level using def, at least within a namespace. So you can rebind a special form symbol, but you can’t evaluate it anywhere else except as the first item of an s-expression.