{Begin SubSec Data Type Predicates} {Title Data Type Predicates} {Text Interlisp provides separate functions for testing whether objects are of certain commonly-used types: {FnDef {FnName LITATOM} {FnArgs X} {Text Returns {lisp T} if {arg X} is a {index Litatoms Term}litatom (see {PageRef Fn LITATOM}) {lisp NIL} otherwise. Note that a number is not a litatom. }} {FnDef {FnName SMALLP} {FnArgs X} {Text Returns {arg X} if {arg X} is a small integer;{index small integers} {lisp NIL} otherwise. (Note that the range of small integers is implementation-dependent. See {PageRef Tag SmallIntegers}.) }} {FnDef {FnName FIXP} {FnArgs X} {Text Returns {arg X} if {arg X} is a small or large integer; {lisp NIL} otherwise.{index integers}{index small integers}{index large integers} }} {FnDef {FnName FLOATP} {FnArgs X} {Text Returns {arg X} if {arg X} is a floating point number;{index floating point numbers} {lisp NIL} otherwise. }} {FnDef {FnName NUMBERP} {FnArgs X} {Text Returns {arg X} if {arg X} is a number{index numbers} of any type ({lisp FIXP} or {lisp FLOATP}), {lisp NIL} otherwise. }} {FnDef {FnName ATOM} {FnArgs X} {Text Returns {lisp T} if {arg X} is an atom{index Atoms Term} (i.e. a litatom or a number); {lisp NIL} otherwise. Warning: {lisp (ATOM {arg X})} is {lisp NIL} if {arg X} is an array, string, etc. In many dialects of Lisp, the function {fn ATOM} is defined equivalent to the Interlisp function {fn NLISTP}. }} {FnDef {FnName LISTP} {FnArgs X} {Text Returns {arg X} if {arg X} is a list cell,{index list cells} e.g., something created by {fn CONS}; {lisp NIL} otherwise. }} {FnDef {FnName NLISTP} {FnArgs X} {Text {lisp (NOT (LISTP X))}. Returns {lisp T} if {arg X} is not a list cell, {lisp NIL} otherwise. }} {FnDef {FnName STRINGP} {FnArgs X} {Text Returns {arg X} if {arg X} is a string,{index strings} {lisp NIL} otherwise. }} {FnDef {FnName ARRAYP} {FnArgs X} {Text Returns {arg X} if {arg X} is an array,{index arrays} {lisp NIL} otherwise. Note: In some implementations of Interlisp (but not Interlisp-D), {fn ARRAYP} may also return {arg X} if it is of type {lisp CCODEP} or {lisp HARRAYP}. }} {FnDef {FnName HARRAYP} {FnArgs X} {Text Returns {arg X} if it is a hash array object; otherwise {lisp NIL}. Note that {fn HARRAYP} returns {lisp NIL} if {arg X} is a list whose {fn CAR} is an {lisp HARRAYP}, even though this is accepted by the hash array functions. }} Note: The empty list, {lisp ()} or {lisp NIL},{index NIL Litatom} is considered to be a litatom, rather than a list. Therefore, {lisp (LITATOM NIL)} = {lisp (ATOM NIL)} = {lisp T} and {lisp (LISTP NIL)} = {lisp NIL}. Care should be taken when using these functions if the object may be the empty list {lisp NIL}. }{End SubSec Data Type Predicates} {Begin SubSec Equality Predicates} {Title Equality Predicates} {Text A common operation when dealing with data objects is to test whether two objects are equal. In some cases, such as when comparing two small integers, equality can be easily determined. However, sometimes there is more than one type of equality. For instance, given two lists, one can ask whether they are exactly the same object, or whether they are two distinct lists which contain the same elements. Confusion between these two types of equality is often the source of program errors. Interlisp supplies an extensive set of functions for testing equality: {FnDef {FnName EQ} {FnArgs X Y} {Text Returns {lisp T} if {arg X} and {arg Y} are identical pointers; {lisp NIL} otherwise. {fn EQ} should not be used to compare two numbers, unless they are small integers; use {fn EQP} instead. {note it is safe to use EQ for litatoms and small integers --- for all other objects you can have copies---so be careful} }} {FnDef {FnName NEQ} {FnArgs X Y} {Text {lisp (NOT (EQ {arg X} {arg Y}))} }} {FnDef {FnName NULL} {FnArgs X}} {FnDef {FnName NOT} {FnArgs X} {Text {lisp (EQ {arg X} NIL)} }} {FnDef {FnName EQP} {FnArgs X Y} {Text {index *PRIMARY* EQP Fn}Returns {lisp T} if {arg X} and {arg Y} are {fn EQ}, or if {arg X} and {arg Y} are numbers and are equal in value; {lisp NIL} otherwise. For more discussion of {fn EQP} and other number functions, see {PageRef Tag Numbers}. Note: {fn EQP} also can be used to compare stack pointers ({PageRef Tag EQPStackPointers}) and compiled code ({PageRef Tag EQPCompiledCode}). }} {FnDef {FnName EQUAL} {FnArgs X Y} {Text {fn EQUAL} returns {lisp T} if {arg X} and {arg Y} are (1) {fn EQ}; or (2) {fn EQP}, i.e., numbers with equal value; or (3) {fn STREQUAL}, i.e., strings containing the same sequence of characters; or (4) lists and {fn CAR} of {arg X} is {fn EQUAL} to {fn CAR} of {arg Y}, and {fn CDR} of {arg X} is {fn EQUAL} to {fn CDR} of {arg Y}. {fn EQUAL} returns {lisp NIL} otherwise. Note that {fn EQUAL} can be significantly slower than {fn EQ}. A loose description of {fn EQUAL} might be to say that {arg X} and {arg Y} are {fn EQUAL} if they print out the same way.{note but... exceptions to point out??} }} {FnDef {FnName EQUALALL} {FnArgs X Y} {Text Like {fn EQUAL}, except it descends into the contents of arrays, hash arrays, user data types, etc. Two non-{fn EQ} arrays may be {fn EQUALALL} if their respective componants are {fn EQUALALL}. }} }{End SubSec Equality Predicates} {Begin SubSec Logical Predicates} {Title Logical Predicates} {Text {FnDef {FnName AND} {FnArgs X{SUB 1} X{SUB 2} {ellipsis} X{SUB N}} {Type NOSPREAD NLAMBDA} {Text Takes an indefinite number of arguments (including zero), that are evaluated in order. If any argument evaluates to {lisp NIL}, {fn AND} immediately returns {lisp NIL} (without evaluating the remaining arguments). If all of the arguments evaluate to non-{lisp NIL}, the value of the last argument is returned. {lisp (AND) => T}. }} {FnDef {FnName OR} {FnArgs X{SUB 1} X{SUB 2} {ellipsis} X{SUB N}} {Type NOSPREAD NLAMBDA} {Text Takes an indefinite number of arguments (including zero), that are evaluated in order. If any argument is non-{lisp NIL}, the value of that argument is returned by {fn OR} (without evaluating the remaining arguments). If all of the arguments evaluate to {lisp NIL}, {lisp NIL} is returned. {lisp (OR) => NIL}. }} {fn AND} and {fn OR} can be used as simple logical connectives, but note that they may not evaluate all of their arguments. This makes a difference if the evaluation of some of the arguments causes side-effects. Another result of this implementation of {fn AND} and {fn OR} is that they can be used as simple conditional statements. For example: {lisp (AND (LISTP {arg X}) (CDR {arg X}))} returns the value of {lisp (CDR {arg X})} if {arg X} is a list cell, otherwise it returns {lisp NIL} without evaluating {lisp (CDR {arg X})}. In general, this use of {fn AND} and {fn OR} should be avoided in favor of more explicit conditional statements in order to make programs more readable. }{End SubSec Logical Predicates} {Begin SubSec The COND Conditional Function} {Title The COND Conditional Function} {Text {FnDef {FnName COND} {FnArgs CLAUSE{SUB 1} CLAUSE{SUB 2} {ellipsis} CLAUSE{SUB K}} {Type NOSPREAD NLAMBDA} {Text The conditional function of Interlisp, {fn COND}, takes an indefinite number of arguments, called {index COND clause}clauses. Each {arg CLAUSE{sub i}} is a list of the form {lisp ({arg P{sub i}} {arg C{sub i1}} {ellipsis} {arg C{sub iN}})}, where {arg P{sub i}} is the predicate, and {arg C{sub i1}} {ellipsis} {arg C{sub iN}} are the consequents. The operation of {fn COND} can be paraphrased as: IF {arg P{sub 1}} THEN {arg C{sub 11}} {ellipsis} {arg C{sub 1N}} ELSEIF {arg P{sub 2}} THEN {arg C{sub 21}} {ellipsis} {arg C{sub 2N}} ELSEIF {arg P{sub 3}} {ellipsis} The clauses are considered in sequence as follows: the predicate {arg P{sub 1}} of the clause {arg CLAUSE{sub i}} is evaluated. If the value of {arg P{sub 1}} is "true" (non-{lisp NIL}), the consequents {arg {arg C{sub i1}} {ellipsis} {arg C{sub iN}}} are evaluated in order, and the value of the {fn COND} is the value of {arg C{sub iN}}, the last expression in the clause. If {arg P{sub 1}} is "false" ({fn EQ} to {lisp NIL}), then the remainder of {arg CLAUSE{sub i}} is ignored, and the next clause, {arg CLAUSE{sub i+1}}, is considered. If no {arg P{sub i}} is true for {it any} clause, the value of the {fn COND} is {lisp NIL}. Note: If a clause has no consequents, and has the form {lisp ({arg P{sub i}})}, then if {arg P{sub i}} evaluates to non-{lisp NIL}, it is returned as the value of the {lisp COND}. It is only evaluated once. Example: {lispcode _ (DEFINEQ (DOUBLE (X) (COND ((NUMBERP X) (PLUS X X)) ((STRINGP X) (CONCAT X X)) ((ATOM X) (PACK* X X)) (T (PRINT "unknown") X) ((HORRIBLE-ERROR))] (DOUBLE) _ (DOUBLE 5) 10 _ (DOUBLE "FOO") "FOOFOO" _ (DOUBLE 'BAR) BARBAR _ (DOUBLE '(A B C)) "unknown" (A B C)} A few points about this example: Notice that {lisp 5} is both a number and an atom, but it is "caught" by the {fn NUMBERP} clause before the {fn ATOM} clause. Also notice the predicate {lisp T}, which is always true. This is the normal way to indicate a {fn COND} clause which will always be executed (if none of the preceeding clauses are true). {lisp (HORRIBLE-ERROR)} will never be executed. }} }{End SubSec The COND Conditional Function} {Begin SubSec The IF Statement} {Title The IF Statement} {Text {index *PRIMARY* IF (Statement)} {index *PRIMARY* IF-THEN-ELSE statements} The {lisp IF} statement provides a way of way of specifying conditional expressions that is much easier and readable than using the {fn COND} function directly ({PageRef Fn COND}). CLISP translates expressions employing {lisp IF}, {lisp THEN}, {lisp ELSEIF}, or {lisp ELSE} (or their lowercase versions) into equivalent {fn COND} expressions. In general, statements of the form: {lispcode (if {arg AAA} then {arg BBB} elseif {arg CCC} then {arg DDD} else {arg EEE})} are translated to: {lispcode (COND ({arg AAA} {arg BBB}) ({arg CCC} {arg DDD}) (T {arg EEE}) )} The segment between {lisp IF} or {lisp ELSEIF} and the next {lisp THEN} corresponds to the predicate of a {lisp COND} clause, and the segment between {lisp THEN} and the next {lisp ELSE} or {lisp ELSEIF} as the consequent(s). {lisp ELSE} is the same as {lisp ELSEIF T THEN}. These words are spelling corrected using the spelling list {var CLISPIFWORDSPLST}.{index CLISPIFWORDSPLST Var} Lower case versions ({lisp if}, {lisp then}, {lisp elseif}, {lisp else}) may also be used. If there is nothing following a {lisp THEN}, or {lisp THEN} is omitted entirely, then the resulting {fn COND} clause has a predicate but no consequent. For example, {lisp (if X then elseif {ellipsis})} and {lisp (if X elseif {ellipsis})} both translate to {lisp (COND (X) {ellipsis})}, which means that if {lisp X} is not {lisp NIL}, it is returned as the value of the {fn COND}. Note that only one expression is allowed as the predicate, but multiple expressions are allowed as the consequents after {lisp THEN} or {lisp ELSE}. Multiple consequent expressions are implicitely wrapped in a {fn PROGN}, and the value of the last one is returned as the value of the consequent. For example: {lispcode (if X then (PRINT "FOO") (PRINT "BAR") elseif Y then (PRINT "BAZ"))} CLISP considers {lisp IF}, {lisp THEN}, {lisp ELSE}, and {lisp ELSEIF} to have lower precedence than all infix and prefix operators, as well as Interlisp forms, so it is sometimes possible to omit parentheses around predicate or consequent forms. For example, {lisp (if FOO X Y then {ellipsis})} is equivalent to {lisp (if (FOO X Y) then {ellipsis})}, and {lisp (if X then FOO X Y else {ellipsis})} as equivalent to {lisp (if X then (FOO X Y) else {ellipsis})}. Essentially, CLISP determines whether the segment between {lisp THEN} and the next {lisp ELSE} or {lisp ELSEIF} corresponds to one form or several and acts accordingly, occasionally interacting with the user to resolve ambiguous cases. Note that if {lisp FOO} is bound as a variable, {lisp (if FOO then {ellipsis})} is translated as {lisp (COND (FOO {ellipsis}))}, so if a call to the {it function} {lisp FOO} is desired, use {lisp (if (FOO) then {ellipsis})}. {note actually, in this last example: if FOO bound -> assume FOO is var if FOO unbound,def -> assume FOO is fn if FOO unbound,undef -> ask user "=(FOO)?"} }{End SubSec The IF Statement} {Begin SubSec Selection Functions} {Title Selection Functions} {Text {FnDef {FnName SELECTQ} {FnArgs X CLAUSE{SUB 1} CLAUSE{SUB 2} {ellipsis} CLAUSE{SUB K} DEFAULT} {Type NOSPREAD NLAMBDA} {Text Selects a form or sequence of forms based on the value of {arg X}. Each clause {arg CLAUSE{sub i}} is a list of the form {lisp ({arg S{sub i}} {arg C{sub i1}} {ellipsis} {arg C{sub iN}})} where {arg S{sub i}} is the selection key. The operation of {fn SELECTQ} can be paraphrased as: IF {arg X} = {arg S{sub 1}} THEN {arg C{sub 11}} {ellipsis} {arg C{sub 1N}} ELSEIF {arg X} = {arg S{sub 2}} THEN {ellipsis} ELSE {arg DEFAULT}. If {arg S{sub i}} is an atom, the value of {arg X} is tested to see if it is {fn EQ} to {arg S{sub i}} (which is not evaluated). If so, the expressions {arg C{sub i1}} {ellipsis} {arg C{sub iN}} are evaluated in sequence, and the value of the {fn SELECTQ} is the value of the last expression evaluated, i.e., {arg C{sub iN}}. If {arg S{sub i}} is a list, the value of {arg X} is compared with each element (not evaluated) of {arg S{sub i}}, and if {arg X} is {fn EQ} to any one of them, then {arg C{sub i1}} {ellipsis} {arg C{sub iN}} are evaluated as above. If {arg CLAUSE{sub i}} is not selected in one of the two ways described, {arg CLAUSE{sub i+1}} is tested, etc., until all the clauses have been tested. If none is selected, {arg DEFAULT} is evaluated, and its value is returned as the value of the {fn SELECTQ}. {arg DEFAULT} must be present. An example of the form of a {fn SELECTQ} is: {lispcode [SELECTQ MONTH (FEBRUARY (if (LEAPYEARP) then 29 else 28)) ((APRIL JUNE SEPTEMBER NOVEMBER) 30) 31]} If the value of {lisp MONTH} is the litatom {lisp FEBRUARY}, the {fn SELECTQ} returns 28 or 29 (depending on {lisp (LEAPYEARP)}); otherwise if {lisp MONTH} is {lisp APRIL}, {lisp JUNE}, {lisp SEPTEMBER}, or {lisp NOVEMBER}, the {fn SELECTQ} returns 30; otherwise it returns 31. {fn SELECTQ} compiles open, and is therefore very fast; however, it will not work if the value of {arg X} is a list, a large integer, or floating point number, since {fn SELECTQ} uses {fn EQ} for all comparisons. }} Note: {lisp SELCHARQ} ({PageRef (Macro) SELCHARQ}) is a version of {fn SELECTQ} that recognizes {fn CHARCODE} litatoms. {FnDef {FnName SELECTC} {FnArgs X CLAUSE{SUB 1} CLAUSE{SUB 2} {ellipsis} CLAUSE{SUB K} DEFAULT} {Type NOSPREAD NLAMBDA} {Text "{fn SELECTQ}-on-Constant." Similar to {fn SELECTQ} except that the selection keys are evaluated, and the result used as a {fn SELECTQ}-style selection key. {fn SELECTC} is compiled as a {fn SELECTQ}, with the selection keys evaluated at compile-time. Therefore, the selection keys act like compile-time constants (see {PageRef Fn CONSTANT}). For example: {lispcode [SELECTC NUM ( (for X from 1 to 9 collect (TIMES X X)) "SQUARE" ) "HIP"]} compiles as: {lispcode [SELECTQ NUM ( (1 4 9 16 25 36 49 64 81) "SQUARE" ) "HIP"]} }} {Begin Note} SELECT de-documented. It doesn't seem to be used in system files (EXCEPT as a PROG tag). Def moved to Stuff.SELECT. Ron says: SELECTxx fns should be conceptually like (SEL FN X . CLAUSES), where FN is wrapped around each select key (one atom or one list) so (SELECTQ ...) <==> (SEL 'QUOTE ...) (SELECTC ...) <==> (SEL 'CONSTANT ...) (SELCHARQ ...) <==> (SEL 'CHARCODE ...) so far so good...but unfortunately SELECT has somewhat different semantics, so it doesn't fit into this model. In particular, (SELECT X ... ( (A B C) ...) ...) evaluates A, B, and C individually, rather than doing something to (A B C). Also, compiled SELECT doesn't do the right thing if infix operators are in selection keys or rest of clauses. {End Note} }{End SubSec Selection Functions}