[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [Page Top / Bottom] |
This section is also intended as a bibliography. All the books and papers listed here should be considered recommended reading.
(setq fact #'(lambda (f n) (if (= n 0) 1 (* n (funcall f f (- n 1)))))) |
and then passing it to a function that will return a closure in which original function is called on itself:
(defun recurser (fn) #'(lambda (&rest args) (apply fn fn args))) |
Passing fact to this function yields a regular factorial function,
> (funcall (recurser fact) 8) 40320 |
which could have been expressed directly as:
((lambda (f) #'(lambda (n) (funcall f f n))) #'(lambda (f n) (if (= n 0) 1 (* n (funcall f f (- n 1)))))) |
Many Common Lisp users will find labels or alambda more convenient.
Testing triangle in one implementation, Gabriel found that "even when the C compiler is provided with hand-generated register allocation information, the Lisp code is 17% faster than an iterative C version of this function." His paper mentions several other programs which ran faster in Lisp than in C, including one that was 42% faster.
(defun compall () (do-symbols (s) (when (fboundp s) (unless (compiled-function-p (symbol-function s)) (print s) (compile s))))) |
This function also prints the name of each function as it is compiled.
(defun our-nreverse (lst) (if (null (cdr lst)) lst (prog1 (nr2 lst) (setf (cdr lst) nil)))) (defun nr2 (lst) (let ((c (cdr lst))) (prog1 (if (null (cdr c)) c (nr2 c)) (setf (cdr c) lst)))) |
People know intuitively that design is easier when one has a broad view of one's work. This is why easel painters use long-handled brushes, and often step back from their work. This is why generals position themselves on high ground, even if they are thereby exposed to enemy fire. And it is why programmers spend a lot of money to look at their programs on large displays instead of small ones.
Dense programs make the most of one's field of vision. A general cannot shrink a battle to fit on a table-top, but Lisp allows you to perform corresponding feats of abstraction in programs. And the more you can see of your program at once, the more likely it is to turn out as a unified whole.
This is not to say that one should make one's programs shorter at any cost. If you take all the newlines out of a function, you can fit it on one line, but this does not make it easier to read. Dense code means code which has been made smaller by abstraction, not text-editing.
Imagine how hard it would be to program if you had to look at your code on a display half the size of the one you're used to. Making your code twice as dense will make programming that much easier.
(defun filter (fn lst) (delete nil (mapcar fn lst))) (defun filter (fn lst) (mapcan #'(lambda (x) (let ((val (funcall fn x))) (if val (list val)))) lst)) (defun group (source n) (if (endp source) nil (let ((rest (nthcdr n source))) (cons (if (consp rest) (subseq source 0 n) source) (group rest n))))) (defun flatten (x) (mapcan #'(lambda (x) (if (atom x) (mklist x) (flatten x))) x)) (defun prune (test tree) (if (atom tree) tree (mapcar #'(lambda (x) (prune test x)) (remove-if #'(lambda (y) (and (atom y) (funcall test y))) tree)))) |
> (find2 #'oddp '(2 . 3)) >>Error: 3 is not a list. |
CLTL2 (p. 31) says that it is an error to give a dotted list to a function expecting a list. Implementations are not required to detect this error; some do, some don't. The situation gets murky with functions that take sequences generally. A dotted list is a cons, and conses are sequences, so a strict reading of CLTL would seem to require that
(find-if #'oddp '(2 . 3)) |
return nil instead of generating an error, because find-if is supposed to take a sequence as an argument.
Implementations vary here. Some generate an error anyway, and others return nil. However, even implementations which follow the strict reading in the case above tend to deviate in e.g. the case of (concatenate 'cons '(a . b) '(c . d)), which is likely to return (a c . d) instead of (a c).
In this book, the utilities which expect lists expect proper lists. Those which operate on sequences will accept dotted lists. However, in general it would be asking for trouble to pass dotted lists to any function that wasn't specifically intended for use on them.
(defun rfind-if (fn tree) (if (funcall fn tree) tree (if (atom tree) nil (or (rfind-if fn (car tree)) (and (cdr tree) (rfind-if fn (cdr tree))))))) |
The function passed as the first argument would then have to apply to both atoms and lists:
> (rfind-if (fint #'atom #'oddp) '(2 (3 4) 5)) 3 > (rfind-if (fint #'listp #'cddr) '(a (b c d e))) (B C D E) |
A special form is so called because its evaluation is treated as a special case. In an interpreter, you could imagine eval as a big cond expression:
(defun eval (expr env) (cond ... ((eq (car expr) 'quote) (cadr expr)) ... (t (apply (symbol-function (car expr)) (mapcar #'(lambda (x) (eval x env)) (cdr expr)))))) |
Most expressions are handled by the default clause, which says to get the function referred to in the car, evaluate all the arguments in the cdr, and return the result of applying the former to the latter. However, an expression of the form (quote x) should not be treated this way: the whole point of a quote is that its argument is not evaluated. So eval has to have one clause which deals specifically with quote.
Language designers regard special forms as something like constitutional amendments. It is necessary to have a certain number, but the fewer the better. The special forms in Common Lisp are listed in CLTL2, p. 73.
The preceding sketch of eval is inaccurate in that it retrieves the function before evaluating the arguments, whereas in Common Lisp the order of these two operations is deliberately unspecified. For a sketch of eval in Scheme, see Abelson and Sussman, p. 299.
However, macros generally do not preserve the Lisp evaluation rule. (If one did, you could have used a function instead.) In principle, each macro defines its own evaluation rule, and the reader can't know what it is without reading the macro's definition. So a macro, depending on how clear it is, may have to save much more than its own length in order to justify its existence.
There is good cause to believe that this is merely an oversight. Usually if the order of some operations is unspecified, CLTL will say so. And there is no reason that the order of evaluation of the initforms of a do should be unspecified, since the evaluation of a let is left-to-right, and so is the evaluation of the stepforms in do itself.
> (gentemp) T1 > (setq t2 1) 1 > (gentemp) T3 |
and so tries to ensure that the symbol created will be unique. However, it is still possible to imagine name conflicts involving symbols created by gentemp. Though gentemp can guarantee to produce a symbol not seen before, it cannot foresee what symbols might be encountered in the future. Since gensyms work perfectly well and are always safe, why use gentemp? Indeed, for macros the only advantage of gentemp is that the symbols it makes can be written out and read back in, and in such cases they are certainly not guaranteed to be unique.
(mvpsetq (w x) (values y z) ...) |
we could say
(psetf (values w x) (values y z) ...) |
Defining an inversion for values would also render multiple-value-setq unnecessary. Unfortunately, as things stand in Common Lisp it is impossible to define such an inversion; get-setf-method won't return more than one store variable, and presumably the expansion function of psetf wouldn't know what to do with them if it did.
For example, it might be useful to have a macro insist which took certain expressions of the form (predicate . arguments), and would make them true if they weren't already. As setf has to be told how to invert references, this macro would have to be told how to make expressions true. In the general case, such a macro call might amount to a call to Prolog.
The following expression returns a list, from longest to shortest, of all the symbols visible in the current package:
(let ((syms nil)) (do-symbols (s) (push s syms)) (sort syms #'(lambda (x y) (> (length (symbol-name x)) (length (symbol-name y)))))) |
(defmacro propmacro (propname) `(defmacro ,propname (obj) `(get ,obj ',propname))) |
But CLTL2 does not explicitly state whether the propname form originally passed to propmacro is part of the lexical environment in which the inner defmacro occurs. In principle, it seems that if color were defined with (propmacro color), it should be equivalent to:
(let ((propname 'color)) (defmacro color (obj) `(get ,obj ',propname))) |
or
(let ((propname 'color)) (defmacro color (obj) (list 'get obj (list 'quote propname)))) |
However, in at least some CLTL2 implementations, the new version of propmacro does not work.
In CLTL1, the expansion function of a macro was considered to be defined in the null lexical environment. So for maximum portability, macro definitions should avoid using the enclosing environment anyway.
For a description of unification, see: Nilsson, Nils J. Problem-Solving Methods in Artificial Intelligence. McGraw-Hill, New York, 1971, pp. 175-178.
(if-match (?x . ?rest1) lst1 (if-match (?x . ?rest2) lst2 ?x)) |
In this case, the second ?x is a new variable. If both lst1 and lst2 had at least one element, this expression would always return the car of lst2. However, since you can use (non-?ed) Lisp variables in the pattern of an if-match, you can get the desired effect by writing:
(if-match (?x . ?rest1) lst1 (let ((x ?x)) (if-match (x . ?rest2) lst2 ?x))) |
The restriction, and the solution, apply to the with-answer and with-inference macros defined in Chapters 19 and 24 as well.
`(,v (binding ',v ,binds))) |
in with-answer with:
`(,v (aif2 (binding ',v ,binds) it unbound)) |
This report, and various implementations of Scheme, were at the time of printing available by anonymous FTP from altdorf.ai.mit.edu:pub.
(defmacro fun (x) `(=fun *cont* ,x)) (defmacro fun (x) (let ((fn '=fun)) `(,fn *cont* ,x))) `(defmacro ,name ,parms (let ((fn ',f)) `(,fn *cont* ,,@parms))) `(defmacro ,name ,parms `(,',f *cont* ,,@parms)) |
(setq *cont* #'(lambda (&rest args) (if (cdr args) args (car args)))) |
(define *paths* ()) (define failsym '@) (define (choose choices) (if (null? choices) (fail) (call-with-current-continuation (lambda (cc) (push *paths* (lambda () (cc (choose (cdr choices))))) (car choices))))) (call-with-current-continuation (lambda (cc) (define (fail) (if (null? *paths*) (cc failsym) ((pop *paths*)))))) |
For more on T, see: Rees, Jonathan A., Norman I. Adams, and James R. Meehan. The T Manual, 5th Edition. Yale University Computer Science Department, New Haven, 1988.
The T manual, and T itself, were at the time of printing available by anonymous FTP from hing.lcs.mit.edu:pub/t3.1.
(define *paths* ()) (define failsym '@) (define *choice-pts* (make-symbol-table)) (define-syntax (true-choose choices) `(choose-fn ,choices ',(generate-symbol t))) (define (choose-fn choices tag) (if (null? choices) (fail) (call-with-current-continuation (lambda (cc) (push *paths* (lambda () (cc (choose-fn (cdr choices) tag)))) (if (mem equal? (car choices) (table-entry *choice-pts* tag)) (fail) (car (push (table-entry *choice-pts* tag) (car choices)))))))) |
In this version, true-choose becomes a macro. (The T define-syntax is like defmacro except that the macro name is put in the car of the parameter list.) This macro expands into a call to choose-fn, a function like the depth-first choose defined in Figure 22.4, except that it takes an additional tag argument to identify choice-points. Each value returned by a true-choose is recorded in the global hash-table *choice-pts*. If a given true-choose is about to return a value it has already returned, it fails instead. There is no need to change fail itself; we can use the fail defined on page 396.
This implementation assumes that paths are of finite length. For example, it would allow path as defined in Figure 22.13 to find a path from a to e in the graph displayed in Figure 22.11 (though not necessarily a direct one). But the true-choose defined above wouldn't work for programs with an infinite search-space:
(define (guess x) (guess-iter x 0)) (define (guess-iter x g) (if (= x g) g (guess-iter x (+ g (true-choose '(-1 0 1)))))) |
With true-choose defined as above, (guess n) would only terminate for non-positive n.
How we define a correct choose also depends on what we call a choice point. This version treats each (textual) call to true-choose as a choice point. That might be too restrictive for some applications. For example, if two-numbers (page 291) used this version of choose, it would never return the same pair of numbers twice, even if it was called by several different functions. That might or might not be what we want, depending on the application.
Note also that this version is intended for use only in compiled code. In interpreted code, the macro call might be expanded repeatedly, each time generating a new gensymed tag.
(defnode mods (cat n mods/n ((lambda (regs) (append (butlast regs) (setr a 1 (last regs))))) (setr mods *))) |
then following the arc (however deep) would set the the topmost instance of the register a (the one visible when traversing the topmost ATN) to 1.
(=defun prove (query binds) (choose (choose-bind b2 (lookup (car query) (cdr query) binds) (=values b2)) (choose-bind r *rules* (=funcall r query binds)))) |
(defmacro check (expr) `(block nil (with-inference ,expr (return t)))) |
To administrative ears, this sounds like circular reasoning. Such ears would rather hear that Lisp's value hinged on a single, easily understood concept. For years we have tried to oblige them, with little success. Lisp has been described as a "list-processing language," a language for "symbolic computation," and most recently, a "dynamic language." None of these phrases captures more than a fraction of what Lisp is about. When retailed through college textbooks on programming languages, they become positively misleading.
Efforts to sum up Lisp in a single phrase are probably doomed to failure, because the power of Lisp arises from the combination of at least five or six features. Perhaps we should resign ourselves to the fact that the only accurate name for what Lisp offers is Lisp.
> (let ((v #((2 . a) (3 . b) (1 . c) (1 . d)))) (sort (copy-seq v) #'< :key #'car)) #((1 . D) (1 . C) (2 . A) (3 . B)) |
Note that the relative order of the first two elements has been reversed. The built-in stable-sort provides a way of sorting which won't reorder equal elements:
> (let ((v #((2 . a) (3 . b) (1 . c) (1 . d)))) (stable-sort (copy-seq v) #'< :key #'car)) #((1 . C) (1 . D) (2 . A) (3 . B)) |
It is a common error to assume that sort works like stable-sort. Another common error is to assume that sort is nondestructive. In fact, both sort and stable-sort can alter the sequence they are told to sort. If you don't want this to happen, you should sort a copy. The call to stable-sort in get-ancestors is safe because the list to be sorted has been freshly made.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [Page Top / Bottom] |