I'm trying to learn and understand the Lisp programming language to a deep level. The function +
evaluates its arguments in applicative order:
(+ 1 (+ 1 2))
(+ 1 2)
will be evaluated and then (+ 1 3)
will be evaluated, but the if
function works differently:
(if (> 1 2) (not-defined 1 2) 1)
As the form (not-defined 1 2)
isn't evaluated, the program doesn't break.
How can the same syntax lead to different argument evaluation? How is the if
function defined so that its arguments aren't evaluated?
It would not make any sense to do so. Example:
(if (ask-user-should-i-quit) (quit) (continue))
. Should that quit, even though the user does not want to?IF
is not a function in Lisp. It is a special built-in operator. Lisp a several built-in special operators. See: Special Forms. Those are not functions.The arguments are not evaluated as for functions, because
if
is a special operator. Special operators can be evaluated in any arbitrary way, that's why they're called special.Consider e.g.
If the division was always evaluated, there could be a division by zero error which obviously was not intended.
Lisp syntax is regular, much more regular than other languages, but it's still not completely regular: for example in
let
is not the name of a function and((x 0))
is not a bad form in which a list that is not a lambda form has been used in the first position.There are quite a few "special cases" (still a lot less than other languages, of course) where the general rule of each list being a function call is not followed, and
if
is one of them. Common Lisp has quite a few "special forms" (because absolute minimality was not the point) but you can get away for example in a scheme dialect with just five of them:if
,progn
,quote
,lambda
andset!
(or six if you want macros).While the syntax of Lisp is not totally uniform the underlying representation of code is however quite uniform (just lists and atoms) and the uniformity and simplicity of representation is what facilitates metaprogramming (macros).
"Lisp has no syntax" is a statement with some truth in it, but so it's the statement "Lisp has two syntaxes": one syntax is what uses the
reader
to convert from character streams to s-expressions, another syntax is what uses the compiler/evaluator to convert from s-expressions to executable code.It's also true that Lisp has no syntax because neither of those two levels is fixed. Differently from other programming languages you can customize both the first step (using reader macros) and the second step (using macros).
if
is a special operator, not an ordinary function.This means that the normal rule that the
rest
elements in the compound form are evaluated before the function associated with thefirst
element is invoked is not applicable (in that it is similar to macro forms).The way this is implemented in a compiler and/or an interpreter is that one looks at the compound form and decides what to do with it based on its
first
element:Note that some special forms can be defined as macros expanding to other special forms, but some special forms must actually be present.
E.g., one can define
if
in terms ofcond
:and vice versa (much more complicated - actually,
cond
is a macro, usually expanding into a sequence ofif
s).PS. Note that the distinction between system-supplied macros and special operators, while technically crisp and clear (see
special-operator-p
andmacro-function
), is ideologically blurred becausesds's answer answers this question well, but there are a few more general aspects that I think are worth mentioning. As that answer and others have pointed out,
if
, is built into the language as a special operator, because it really is a kind of primitive. Most importantly,if
is not a function.That said, the functionality of
if
can be achieved using just functions and normal function calling where all the arguments are evaluated. Thus, conditionals can be implemented in the lambda calculus, on which languages in the family are somewhat based, but which doesn't have a conditional operator.In the lambda calculus, one can define true and false as functions of two arguments. The arguments are presumed to be functions, and true calls the first of its arguments, and false calls the second. (This is a slight variation of Church booleans which simply return their first or second argument.)
(This is obviously a departure from boolean values in Common Lisp, where
nil
is false and anything else is true.) The benefit of this, though, is that we can use a boolean value to call one of two functions, depending on whether the boolean is true or false. Consider the Common Lisp form:If were were using the booleans as defined above, then evaluating
some-condition
will produce eithertrue
orfalse
, and if we were to call that result with the argumentsthen only one of those would be called, so only one of
then-part
andelse-part
would actually be evaluated. In general, wrapping some forms up in alambda
is a good way to be able delay the evaluation of those forms.The power of the Common Lisp macro system means that we could actually define an
if
macro using the types of booleans described above:With these definitions, some code like this:
expands to this:
In general, if there is some form and some of the arguments aren't getting evaluated, then the (
car
of the) form is either a Common Lisp special operator or a macro. If you need to write a function where the arguments will be evaluated, but you want some forms not to be evaluated, you can wrap them up inlambda
expressions and have your function call those anonymous functions conditionally.This is a possible way to implement
if
, if you didn't already have it in the language. Of course, modern computer hardware isn't based on a lambda calculus interpreter, but rather on CPUs that have test and jump instructions, so it's more efficient for the language to provideif
a primitive and to compile down to the appropriate machine instructions.If isn't a function, it's a special form. If you wanted to implement similar functionality yourself, you could do so by defining a macro rather than a function.
This answer applies to Common Lisp, but it'll probably the same for most other Lisps (though in some
if
may be a macro rather than a special form).