====== 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 LISP((In Common LISP, there is only 25 special operators, all other control structures are defined as macros built upon them.)), for example ''IF'', ''LET'', ''QUOTE'', etc. All LabLISP special forms are listed [[ch2_1_spec_ops|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 [[ch1_6_macros|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.((like in Scheme language))
>((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 #'+ #'-))
(# . #) ; 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
----
next → [[ch1_4_conscells|List construction with conscells]]