Table of Contents
Forms and evaluation rules
In LISP, we call any piece of code an expression (symbolic expression, or s-expression). The expressions are structured as lists, and a closed list that has certain syntactic order is called a form. The simplest and most common is a form that applies a function.
Evaluating functions
In LISP’s functional style, the whole program is written as a single form and running the program means evaluating that form. Let us consider pure functional code
(multiply (add 1 5) (subtract 9 2))
As in the very first code example in this manual, here we see a list with one symbol and two sub-lists. Each sub-list contains a symbol and two integer literals. The LISP reader takes the source text above and creates the list data structure with literal and symbol atoms. The symbols multiply
, add
and subtract
are not defined in LISP, but for the purpose of this example, we can assume they are. The functions for multiplying, adding, and subtracting do exist in LISP, but under different names, represented by symbols *
+
and –
(also division /
). Correct LISP expression should read
(* (+ 1 5) (- 9 2))
We easily understand now that such form will evaluate first to (* 6 7)
and that will evaluate to 42. However, the mathematical operators *
+
-
do not have any special role, they are just short and simple symbol names that evaluate to functional objects, when appearing at the beginning of a list.
The expression (multiply (add 1 5) (subtract 9 2))
is evaluated (evaled) in following manner: First, the eval looks at the symbol multiply
, which evaluates to function-object and the form is treated as function-application form. That means that first the two arguments (add 1 5)
and (subtract 9 2)
are evaluated and the resulting integer atoms are supplied to the multiply
function object which returns integer atom 42
. In the (add 1 5)
form, the symbol add evaluates to function-object and the numbers 1
and 5
are evaluated, but they are already literal atoms, so they evaluate to themselves. The same happens in the form (subtract 9 2)
.
The previous example contains only pure functions, which means that for given inputs, they always give the same output and perform no side-effects. Practically, we need functions that do have side-effects. Consider a hypothetical function call
(move-motor 2 100)
It might be that such function in our control program will start moving with real-world motor number 2 to position 100. This would be the side-effect. As the result of eval, this function might give T
(true) if the motor is running, and NIL
(false) when the motor is disconnected, not available, or something like that.
To complete this explanation, let’s now assume that the functions multiply
, add
and subtract
differ from the *
+
and -
in the way that they always report what they are doing by printing “Multiplying!”, “Adding!” or “Subtracting!” to the output window – this would be their side effect. We will try following programs:
USER>(* (+ 1 5) (- 9 2)) 42
and
USER>(multiply (add 1 5) (subtract 9 2)) “Adding!” “Subtracting!” “Multiplying!” 42
These two programs will give the same result 42, but the first call performs no side effects, while the second does.
Functions in LISP can have any number of arguments. There are functions that need a fixed number of arguments, or functions with fixed and optional arguments. For example, the arithmetic functions +
, -
, *
and /
accept any number of parameters (at least one).
The expression with function name and arguments is called function-application form. Such form is evaluated according to a simple rule. If the first item in the form is a symbol naming a function, the remaining items in the form are evaluated in order, left to right. Only after evaluation of all argument forms, the list of resulting arguments is submitted to the function and the function is applied. If the number of arguments does not match the number accepted by the function, the function is not applied, whole form returns NIL
, and a warning is issued to the user. Important point is that all the arguments are evaled, even if their number does not match the function expectation. We will discuss the function behavior in more details in the following paragraphs.
Boolean T and NIL
With respect to evaluation there are two important symbols in LISP that take the role of Boolean true and false values. The symbol T
evaluates to itself and represents true. The false symbol NIL
evaluates to nothing (null pointer, empty list), but the result will be printed out as NIL
. By definition, the value of symbol NIL
is equivalent to the quoted symbol 'NIL
, equivalent to ()
as empty form, and equivalent to '()
as empty list data. Since these expressions are equivalent, the symbol NIL
is the only false value in LISP. Everything else (i.e. not nil) will be considered true in conditional expressions. From the beginning of this manual, we are discussing that LISP expressions as data structures contain atoms or lists. NIL
is the only thing that is both atom and list, because it isn’t a thing, it’s nothing.
Special forms and macros
Lisp commands are coming in three flavors: functions, special forms and macros. We have already introduced the functions, and the fact that the arguments are always evaluated. Functions have other interesting properties, which will be discussed in the next paragraph. But there are situations where we do not want to have all items evaluated, typically it is the case of some control structures, like IF
.
The forms which define these control structures are called special forms, and the symbol that appears as the first item in such form is called a special operator. There is only limited number of special operators in LISP1), for example IF
, LET
, QUOTE
, etc. All LabLISP special forms are listed here.
Important property is that the special forms control the evaluation of the arguments – they decide which arguments will be evaluated or not, in what order, and might evaluate the arguments more than once (e.g. in loop control structures). Let’s take the fundamental IF
form as an example.
(if c a b)
The symbol IF
is a special operator, so the form is a special form, not a function-application form. The IF-form can have 2 or 3 arguments and will behave as if
in other programming languages: If condition C
is true, then evaluate A
(and return the result), else evaluate B
(and return the result). In case there are only 2 arguments, i.e. (if c a)
, the form returns NIL
when C
is not true (when C
evals to NIL
). The key point is that the IF-form always evaluates the condition C
, but only evaluates one of the options A
, B
, and the other is ignored – so no side effects appear.
User cannot define a special form, but instead can define a macro. The topic of LISP macros is quite complex and very interesting, because macros can be very powerful programming tool. We will discuss the macro-writing capabilities of LabLISP later, but for now we can just introduce macros as procedures that take some forms as arguments, rewrite the code and then evaluate. This means that macros can control if, when, and how many times the arguments are evaluated - just like the special operators do.
Macro can be understood as a runtime syntactic transformation – as a program-writing function. This makes the LISP macro different from e.g. C-language macro that essentially just replaces some text in the source file. LISP macros are expanded during the run of the program and can use any other LISP capabilities (functions, operators, other macros, and the state of the environment) to perform the expansion.
LabLISP does not have any built-in macros.
Symbols and function objects
What makes LISP outstanding from most other programming languages is that LISP program is treated as data structure. We have already seen that variable names are represented by symbols, and symbol is an atomic object in LISP. When we apply the reader on input string “(foo a b)”
, it generates list of three symbols - exactly the same list data structure as could be created with (list 'foo 'a 'b)
.
The subsequent evaluation will have to find a function named by the symbol FOO
, and values bound to the symbols A
and B
. The rule that all LISP forms must follow the prefix notation – the first symbol in the form will name the operator – makes certain that the eval algorithm knows to look for a function named FOO
. One symbol can name function and variable at the same time – LabLISP, like Common LISP has separate namespace for functions and values.
A symbol in an expression then can have three meanings. Let’s have a symbol A
, which will evaluate to its bound value when appearing plainly in an expression. The symbol itself can be obtained with the quoting it: 'A
, which is the same as using the special form (QUOTE A)
. Quote is special operator because the argument is not evaluated. If we want to obtain the function named by the symbol A, we need to use the sharp-quoting: #'A
, which again is just short of special form (FUNCTION A)
.
>(setq a 13) ; we define value of symbol A 13 >(defun a () "Hello Work!") ; also function named A A >a ; evaluate symbol A 13 ; returns the value >(a) ; call function A "Hello Work!" >'a ; quoted symbol A A ; returns the symbol itself
The functions are, in fact, atomic (first class) objects! And as such they can be returned from other functions or supplied as arguments to other functions.
No-error evaluation
Until now, the discussion on this page applied to LabLISP as well as to any other LISP. But while Common LISP is a powerful and universal programming language, LabLISP is a simple high-level scripting tool and has different demands on the user. To prevent the user from falling into some confusing debug modes, LabLISP actually never results in error. Instead, erroneous expressions result in NIL
. The mechanism is similar to how arithmetic errors are treated in C++, where wrong operation (e.g. division by zero) does not raise an exception, but results in NAN (“not a number”), which can still be passed to other operations. In LabLISP, the user is informed that something went wrong by a warning that is printed into the output, but the evaluation continues.
>(/ 10 0) ;Division by zero. NIL
This LabLISP property needs some attention when writing the program, so that NIL
result is not used in any positive sense. Functions and macros should be designed in a way that they can receive a NIL
value in place of a valid argument.
Related to the idea of no-error evaluation is a very experimental LabLISP way of handling some syntax errors – a way that might make some experienced lisp-hackers twitch uncomfortably. LabLisp will not fuss about unmatched parentheses in the forms supplied to the reader. Following forms will run just fine – end of the whole expression is assumed to also end all unclosed forms, and extra closing parentheses are ignored. The reader issues comment, but eval result is produced nonetheless.
>(+ 1 (+ 2 4 ;READER: Ended with 2 unclosed parens 7 >(+ 1 (+ 2 4)))) ;READER: Closing too many parens 7
Lisp syntax requires a symbol naming a function in the first position of the function application form. In LabLISP, the first item is checked first if it is naming any special form (like IF
, LET
etc.), but then it is evaluated with a flag signifying that the interpreter should look in the function namespace. So if the first item is a symbol naming a function (macro), then it evals to a function (resp. macro function) object and LabLisp performs standard function application form (resp. macro expansion form). This would be same in Common LISP – there is separate namespace for functions and value symbols, so in the evaluation of the functor, the function-value of the symbol is retrieved.
But LabLISP is very benevolent about the items that can be used as functions. First, it is possible to write LAMBDA
form in the functor position.2)
>((lambda (a b) (+ a b)) 7 8) 15
Actually, we can use any other form that returns a function object. Let’s consider following example:
USER>(setq ops (cons #'+ #'-)) (#<function +> . #<function ->) ; we created cons with 2 function objects USER>((car ops) 3 4) ; using the + function 7 USER>((cdr ops) 3 4) ; using the - function -1
Further, when a literal atom is placed in the functor position, there will be a constant function – with no expected arguments – created around it and the form will be evaluated as usual function application.
USER>(13) 13
The expression (13)
will be treated as constant function-application form and evaluate to 13. If additional items are in the list, then it will be treated as too many arguments to a function – warning will be raised, and the form returns NIL.
>(13 14 15) ;Adding arguments to constant. The form will collapse to NIL! NIL
This behavior is based on the idea that if literal 13
evals to itself, it can also dabble as a name of a transient constant function which gives 13. And it will also work when a form is in the functor position and it evals to literal value.
Since the function application normally evaluates all arguments before submitting the results to the function object, some sort of PROGN
bypass is in fact possible (not encouraged) with this construct:
>((print "a") (print "b") (print "c")) ; here (PRINT "a") is evaled to "a"-constant function "a" ; printing "a" is the side-effect "b" "c" ;Adding arguments to constant. The form will collapse to NIL! NIL
In the last example, the first print form was evaled and returned a string, which is still handled as a constant function “a”
. The other two print forms are then evaled as arguments, so we see their side effects – the actual printing of the strings.
Exception to the counstruct of constant function is NIL
. When the functor item evals to NIL
(which also happens when the function is not defined), the form is not treated as NIL
-constant function, but as a special nil-form, which does not expect any arguments and returns NIL
. If there are arguments supplied, they are not evaluated.
>(nil (print "b") (print "c")) ; NIL functor is treated as special form ;Adding arguments to NIL form! NIL