
Tom's Lisp Conditional Operators
Lisp is a functional language, meaning that everything is done
by function evaluation.
Since there are no statements,
there can be no if statement; conditional execution
is managed with functions.
This is not really as strange as it seems; the C/C++/Java ?: expression
is similar.
The If Function
The simplest Tom's Lisp conditional
is the if, which takes the form
(if condition thenexpression [ elseexpression ] )
The condition is evaluated. If it produces a true value,
then thenexpression is evaluated to give the value of
the whole if expression. Otherwise, elseexpression is evaluated and
returned. If elseexpression is omitted and condition is false,
the if returns nil.
Ordinary Tom's Lisp functions evaluate all their arguments before
running the function. The if form, like cond described
below, is not ordinary. Much as the if statement in a
procedural language, Tom's Lisp evaluates just the condition first,
then uses the result to decide which branch to evaluate.
The one not chosen is left unevaluated.
Comparisons and Boolean Operations
The C language does not have a boolean type, but uses integer to
do the job, letting zero be false and other values be true.
Tom's Lisp, following other Lisps, takes a similar approach
using lists. The empty list
serves as false and all other values are true. Both
nil and #f are identifiers defined by the system to evaluate to
the empty list, so they can serve as false in most circumstances.
The atom #t is defined to evaluate to itself as a convenient true.
Note that the integer zero is not the empty list, so treated as true.
lsp>#f
nil
lsp>#t
#t
lsp>(< 5 8)
#t
lsp>(if (< 5 8) 12 19)
12
lsp>(if (>= 4 11) "Snake" "Eeel")
"Eeel"
lsp>(if 'moose 'owl 'turkey)
owl
lsp>(if (< 5 3) (/ 3 0) (+ 6 7))
13
The last case demonstrates that the division was not evaluated, since
that would have produced an error.
There are several operators which return true or false values which
can be used in testing. These sorts of functions
are called predicates.
These include the comparisons which operate on pairs of integers:
<, >, <=, >=, = and !=, whose
meaning is straightforward. There is also a group of type
identifiers which tell if their single argument is a particular type of
data:
null? | Tell if the argument is nil (empty list). |
pair? | Tell if the argument is a pair (result of cons,
most often a non-empty list.) |
id? | Tells if the argument is an identifier. |
int? | Tells if the argument is an integer. |
str? | Tells if the argument is a string. |
builtin? | Tells if the argument is a builtin function. |
lambda? | Tells if the argument is a function (result of
the lambda operator). |
macro? | Tells if the argument is a macro (result of
the macro operator). |
functional? | Tells if the argument is something that can be called
with a parameter list as a function is.
These are any of the last three types. |
lsp>(null? '(a b c))
nil
lsp>(null? ())
#t
lsp>(null? 'a)
nil
lsp>(id? 'fred)
#t
lsp>(id? 17)
nil
lsp>(int? 17)
#t
lsp>(builtin? car)
#t
lsp>(functional? 17)
nil
A widely-used
predicate is equal?, which
tells if any two arguments of any type are the same.
It is more general than =, which only operates on
integers.
lsp>(equal? 'fred 'alice)
nil
lsp>(equal? '(fred alice mike) '(fred alice mike))
#t
lsp>(equal? '(fred alice mike) '(fred (alice mike)))
nil
lsp>(equal? 'fred "fred")
nil
lsp>(equal? 'joe 'joe)
#t
lsp>(equal? (cons 17 '(4 19)) (cdr '(3 17 4 19)))
#t
Finally, there is the eq? predicate. It compares the
internal pointers, so it tells if the two data items
are represented by the same internal object.
It is included only because most Lisps have one.
lsp>(eq? nil nil)
#t
lsp>(eq? 'a 'a)
nil
lsp>(eq? '(a b c) '(a b c))
nil
lsp>(eq? 5 5)
nil
lsp>(eq? car car)
#t
The behavior of eq? in any particular Lisp depends on the
implementation. In Tom's Lisp, eq? usually returns false, except
when a stored value is compared to itself.
Once we have have comparisons and predicates to generate boolean values,
it's also nice to have some boolean combinations. Tom's Lisp
provides not which inverts its argument and returns #t or nil.
It also provides or, and, nand and nor, which
are short-circuit and take any number of arguments.
The or operator returns the first argument which is not nil, or
nil. The others always return #t or nil.
lsp>(and (< 5 34) (equal? '(a . b) (cons 'a 'b)) (builtin? cons))
#t
lsp>(not 19)
nil
lsp>(or () (+ 4 5) (< 5 6))
9
The Cond Operator
The other standard conditional construct is happily known
as cond. It is similar to the case or switch
in other languages, but with more general conditions.
It takes the form:
(cond (c1 v1) . . . (cn vn))
The system evaluates c1. If it is
true, v1 is evaluated and returned. Otherwise,
the system goes on to c2. It returns the vi for
the first ci which evaluates to true.
If no condition is true, the form returns nil.
Frequently the last condition, cn, is #t to
assure that vn will be evaluated, if nothing else is.
This operates much like the default label in a
C/C++/Java switch.
lsp>(cond ((< 5 6) 'first) ((> 5 6) 'second) (#t 'third)))
first
lsp>(cond ((< 7 6) 'first) ((> 7 6) 'second) (#t 'third)))
second
lsp>(cons 'a (cond ((< 4 6) '(g l u)) ((> 4 6) '(w e))))
(a g l u)
The Begin Operator
The begin operator takes any number of forms expressions,
evaluates them left to right, and returns the value of the last one.
It is useful when you
need to combine several operations into the branch of an if.
Tomslisp begin is
much like begin/end in Pascal and similar languages, or
curly braces in C and friends.
lsp>(begin (* 4 5) (+ 3 4) (/ 7 5))
1
lsp>(if (< 4 5) (begin (* 4 5) (+ 3 4) (/ 7 5)) 45)
1