Date: Tuesday, 28 September 1982 20:53-EDT From: Scott E. Fahlman To: DLW at MIT-MC Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) Well, the "RPG memorial" as it stands says that only strings can be used as inputs to the special "string-specific" functions, but your reply to me says that actually even non-simple char-arrays can be used as inputs to the string functions. The following text is lifted verbatim from the RPG memorial proposal: "A STRING is a VECTOR whose element-type (specified by the :ELEMENT-TYPE keyword) is STRING-CHAR. Strings are special in that they print using the "..." syntax, and they are legal inputs to a class of "string functions". Actually, these functions accept any 1-D array whose element type is STRING-CHAR. This more general class is called a CHAR-SEQUENCE." Looks to me like I am saying that STRING-mumble accepts true strings and also the more general CHAR-SEQUENCES. At least, that was what I was trying to say. It just seemed too ugly to rename these functions CHAR-SEQUENCE-mumble. I admit that the naming is confusing here, since some objects other than stings are accepted by these functions, but this seems no worse to me than Zetalisp's decision to let the string functions accept symbols as well. The idea is that these are functions that mostly work on strings and, as a special favor, they will also swallow arbitrary 1-D arrays of characters. Also, what does stringp do? Does it ever return t for anything with a fill pointer? If so, then stringp returns t for things that are not strings, which seems unacceptable; if not, then stringp will return NIL for some things that print exactly the same way that strings print, and otherwise behave very similarly, which seems undesirable. In an implementation that distinguishes strings from char-sequences, STRINGP would return T for true (simple) strings and NIL for everything else. If you want to ask if something is a CHAR-SEQUENCE (which includes strings as a subtype), you use CHAR-SEQUENCEP. It is true that non-string char-sequences print as if they were strings, but I don't see the problem with that, unless the association between STRINGP and the "..." syntax has become sacred. -- Scott  Date: Tuesday, 28 September 1982 08:59-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) Well, the "RPG memorial" as it stands says that only strings can be used as inputs to the special "string-specific" functions, but your reply to me says that actually even non-simple char-arrays can be used as inputs to the string functions. So I guess then the name "string" functions isn't very good under this proposal, since these functions aren't really limited to strings, since all strings are vectors but you are telling that you can indeed pass char-arrays-with-fill-pointers to them. What I am saying is that your new proposed nomenclature is confusing in this regard. I admit that this isn't as bad as what the "RPG memorial" message seemed to say (that only strings worked with the string functions), but it still seems sort of suboptimal. Also, what does stringp do? Does it ever return t for anything with a fill pointer? If so, then stringp returns t for things that are not strings, which seems unacceptable; if not, then stringp will return NIL for some things that print exactly the same way that strings print, and otherwise behave very similarly, which seems undesirable. I guess my opinion is that I am opposed although not fatally opposed. -------  Date: Tuesday, 28 September 1982 09:08-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Scott E. Fahlman Cc: Common-Lisp at SU-AI, Kent M. Pitman Subject: Indeed, one of us must be confused. Well, I think the problem that is really at the heart of what KMP is talking about is the same as something I brought up at the meeting regarding TYPEP. KMP wants a function that asks "can this very array support this optional feature", whereas what he is given is a function that asks "might this feature be supported by some implementation given something of this 'type'?". He wants a way to ask whether a given array object really cannot do certain operations. This is a useful thing, but it is not provided mainly because C.L. spends more time worrying about how to make the differences invisible when they ought to be invisible, rather than how to make them visible when they are visible. In the rubout-handler example, VECTORP shouldn't be used; there should be a new function or set of functions to test the array and report whether it can or cannot handle the particular feature in question. VECTORP is really wrong since, in the way KMP is trying to use it, it tests the AND of several unrelated hairy array features; using a specific feature tester predicate would be more powerful, clear, and useful. -------  Date: Tuesday, 28 September 1982 09:03-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Alan Bawden Cc: Common-Lisp at SU-AI Subject: What is this RESTART kludge? I agree that a required tag and no subform is the right thing. I like RESTART because it is an extremely clear way of signalling my intentions, whereas PROG/PROGBODY/TAGBODY is not. It therefore makes my code easier to read. It's OK with me if it's implemented as a macro that turns into a PROGBODY/TAGBODY internally. -------  Date: Tuesday, 28 September 1982 09:03-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Alan Bawden Cc: Common-Lisp at SU-AI Subject: What is this RESTART kludge? I agree that a required tag and no subform is the right thing. I like RESTART because it is an extremely clear way of signalling my intentions, whereas PROG/PROGBODY/TAGBODY is not. It therefore makes my code easier to read. It's OK with me if it's implemented as a macro that turns into a PROGBODY/TAGBODY internally. -------  Date: 28 September 1982 0007-EDT (Tuesday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: RESTART and TAGBODY Yes, indeed, I'm the spazzer to blame for that form that RESTART takes in the sample interpreter, and it did happen by improper copying of the code for RETURN. Sigh. I made it into RESTART and RESTART-FROM to parallel RETURN and RETURN-FROM, but there seems to be universal opposition to this, so I'll rename RESTART-FROM => RESTART if it gets kept. As for TAGBODY versus PROGBODY, it seemed to me that the most important characteristic of it was not that it is used to implement PROG (which is the oldest but by no means most important construct to have such a body), but rather that it bore tags; that is, I chose a name that described it intrinsically rather than extrinsically. I'm not passionate about this. --Guy  Date: Monday, 27 September 1982 23:14-EDT From: Scott E. Fahlman To: Alan Bawden Cc: Common-Lisp at SU-AI Subject: What is this RESTART kludge? I agree with Alan Bawden that RESTART should not have a value-returning subform, and that RESTART-FROM is silly. It is probably also OK to flush (RESTART NIL) and require a non-null block-name. The principal use that I see for RESTART is to allow certain forms to conveniently restart a function from the top, making use of the implicit named block around each defun body. For example, one might test whether an argument is of the proper type or meets some other criterion and, if not, signal the problem with CERROR, asking for a new argument. If the user returns one, it is nice to be able to fix the arg in question, then restart the function. This will not work if the code has changed the value of other argument variables, but it works in most cases. It is true that this could be done by changing the function's body into a PROG and placing a tag at the start (not forgetting to add a RETURN in the proper place), but this is a pain; I find that this is just enough extra hassle to keep me from doing the right thing with a CERROR in some cases. Having RESTART around would cause me (and, I bet, other lazy slobs) to write somewhat better code in these cases, and the RESTART is more self-explanatory than the equivalent PROG/TAG/GO would be. I don't think this is blind gotophobia; on the other hand, if RESTART were flushed, I could probably live with that. I just think it's a nice minor convenience and worth the small additional clutter. By the way, are we converging toward the name TAGBODY? I much prefer PROGBODY for this use. -- Scott  Date: Monday, 27 September 1982 21:49-EDT From: Scott E. Fahlman To: Kent M. Pitman Cc: Common-Lisp at SU-AI Subject: Indeed, one of us must be confused. KMP suggests that "on the LispM there really will not be any vectors and that intuitively the right thing is for nothing to claim to be a vector." All I can say is that my intuition differs from his on this issue. I think it is perfectly intuitive to think of vectors on some implementations as being limited creatures, and that the LispM is a superset in that its vectors can do some extra tricks. In KMP's proposal, it is the implementations that "support vectors" that are the superset -- that seems strange to me. As KMP points out, you cannot debug protable Common Lisp code completely on a system that provides a superset of the Common Lisp functionality, unless a degraded "compatibility-mode" is provided or perhaps some sort of portability checker in the compiler. This is not a new sitution -- it occurs wherever Common Lisp has stopped short of providing the full Zetalisp features. There are many such cases already, and this one doesn't bother me. So the "RPG memorial" proposal still looks OK to me, as it is.  Date: 27 September 1982 03:29-EDT From: Alan Bawden Subject: What is this RESTART kludge? To: Common-Lisp at SU-AI cc: ALAN at MIT-MC First a simple question. What is the subform in a RESTART form good for? According to the interpreter, a RESTART form contains a subform that gets evaluated before the restart happens. Its value is carefully thrown back to the matching BLOCK, and is then ignored. My current best theory is that it is simply a spaz on GLS's part, perhaps because he simply copied the code for RETURN. For the rest of this message I am going to assume that this is the case and that this subform isn't really there. Also in the recent evaluators there are two RESTART special forms. RESTART and RESTART-FROM. Where RESTART could be a macro expanding as: (RESTART) ==> (RESTART-FROM NIL). According to Moon's notes, the decision to have this RESTART form was made at the last meeting, and there it was agreed that the syntax would be: (RESTART [block-name]) presumably with the block-name defaulting to NIL. At the very least I would prefer this to having the additional RETURN-FROM form. [Although perhaps this is evidence that GLS really intends there to be a gratuitious subform in a RESTART?] A somewhat larger complaint I have is that having (RESTART) mean to restart a block named NIL a really BAD idea. Blocks named NIL are frequently produced by macros (like LOOP, DOTIMES, etc.) that would do something totally bogus if you were to try to restart them. A block named NIL means an iteration, and the fact that you can RETURN from it is a convenience because you frequently want to exit a loop in an arbitrary manner, but restarting an iteration is not something that needs to be made convenient. Also I really dread the thought of re-writing all the macros in the world that use DO or PROG to allow for some loser restarting them. If we must have RESTART, then it should ALWAYS take a block name. Finally, I really don't understand why we need to clutter up the language with this RESTART thing in the first place. Why should I prefer (BLOCK FOO ... (RESTART FOO) ...) over (TAGBODY FOO ... (GO FOO) ...)? I have this sneaking suspicion that it has something to do with gotophobia, which is silly. It seems that we have nicely split the actions of PROG into three simpler special forms, and then we are repeating the mistake we made in overloading PROG by adding a new kludge to BLOCK. Wouldn't the following do just as well as a private macro for people who like their code to look this way? (defmacro restartable (name &body body) `(tagbody ,name (progn ,@body))) (defmacro restart (name) `(go ,name)) Alternatives: [in order of decreasing desirability in my opinion] A) Flush it. [Yeah!] B) Have a fifth distinct environment containing restart block names, and a new special form to introduce it (similar to the RESTARTABLE macro defined above). In this case it wouldn't be totally unreasonable to have (RESTART) mean (RESTART NIL), since it wouldn't interact with BLOCKs at all. [In other words, if we have to have it, lets do it right.] C) Install the RESTART and RESTARTABLE macros I defined above. [If we can't do it right, lets build knowledge of it into as few places as possible.] D) Keep the restart block namespace the same as the return block namespace, but specify that a block name must ALWAYS be given to RESTART. [At least let's not have it be a shaft.] E) Keep things as they are now except perhaps for clarifying the bit about the random subform and the need for both RESTART and RESTART-FROM. [The very lesat we can do.]  Date: 27 September 1982 01:08-EDT From: Kent M. Pitman Subject: Indeed, one of us must be confused. To: Fahlman at CMU-10A cc: Common-Lisp at SU-AI No, I think I understood fully what you meant. Nothing in your reply is in conflict with my original message. I suggest that it is you who misunderstood me... My turn now to be less vague. In any case like the LispM where vectors aren't there, then there would be cases of the code in this form: (COND ((VECTORP obj) ...branch X...) (T ...branch Y...)) Now, it may in some sense seem arbitrary whether branch X or Y is taken, so long as it's predicatable, but I argue that there are good reasons why someone should want it to take branch Y in the case where vectors are not being represented. The first is that on the LispM there really will not be any vectors and that intuitively the right thing is for nothing to claim to be a vector. I think the other arguments follow from this but let me lay them out. The next reason is that one cannot debug branch X fully on a system which does not have vectors. You can write code like: (COND ((VECTORP X) ...simple code which needn't know about fill pointers...) (T ...code which might hack fill pointers if they exist...)) For some applications, this code will function incorrectly on an X which is 1-D but has a fill pointer because it may be relevant to some kinds of computation that X does have a fill pointer. Consider: (DEFUN SET-STREAM-RUBOUT-HANDLER-BUFFER (STREAM BUFFER) (COND ((VECTORP BUFFER) (ERROR "Buffer must have all the hairy features of arrays!")) (T (SET-STREAM-RUBOUT-HANDLER-BUFFER-INTERNAL STREAM BUFFER)))) This is a useful error check to put in portable code and would be thwarted by VECTORP returning T on non-vectors. Further, one -could- at least debug branch Y correctly because it would have to worry about the case of fill pointers. It might have to go to more work than was needed on a few inputs, but there are no cases where I think a drastically wrong thing would happen. The reason for this is clear: Even in systems with VECTORs, it is possible to construct 1-D arrays which have all the properties of VECTORs except VECTORness itself. Hence, in an environment like the LispM, this is exactly what you have. A thing with all a VECTOR's properties except one -- VECTORness. And since you can't reliably tell what was made with a VECTOR property and what wasn't, the right thing is to make the default in the safe direction, which I claim is to assume they are not the simple case unless you have proof. -kmp ps Note that there is a third alternative which is to require that implementations not supporting vectors at least support something which is identifiable as a VECTOR. This could be done by storing a special marker in an array leader, by creating a hash table of all vectors, or whatever. I do not advocate this idea, but I do point it out.  Date: 27 Sep 1982 0027-EDT From: STEELE at CMU-20C Subject: Revised proposed evaluator(s) To: common-lisp at SU-AI In response to comments on the proposed sample Common LISP evaluator, I have made these changes: (1) Fixed an EVALHOOK bug; now the variable EVALHOOK is bound to NIL over the invocation of the hook function. (2) Fixed a bug in BLOCK; now the normal return values are properly returned. (3) Fixed PROG to parse the declarations properly and put them in the LET used to bind the variables. (4) EVAL now calls *EVAL, not %EVAL, for parallelism with other versions. Enclosed is the fixed version 1, and also version 2, which uses special variables for VENV, FENV, BENV, and GENV to avoid parameter passing for these slowly-changing variables. (Version 3, which is the bummed version for Spice LISP, is about half-done and not enclosed here.) --Guy ----------------------------------------------------------- ;;; This evaluator splits the lexical environment into four ;;; logically distinct entities: ;;; VENV = lexical variable environment ;;; FENV = lexical function and macro environment ;;; BENV = block name environment ;;; GENV = go tag environment ;;; Each environment is an a-list. It is never the case that ;;; one can grow and another shrink simultaneously; the four ;;; parts could be united into a single a-list. The four-part ;;; division saves consing and search time. ;;; ;;; Each entry in VENV has one of two forms: (VAR VALUE) or (VAR). ;;; The first indicates a lexical binding of VAR to VALUE, and the ;;; second indicates a special binding of VAR (implying that the ;;; special value should be used). ;;; ;;; Each entry in FENV looks like (NAME TYPE . FN), where NAME is the ;;; functional name, TYPE is either FUNCTION or MACRO, and FN is the ;;; function or macro-expansion function, respectively. Entries of ;;; type FUNCTION are made by FLET and LABELS; those of type MACRO ;;; are made by MACROLET. ;;; ;;; Each entry in BENV looks like (NAME NIL), where NAME is the name ;;; of the block. The NIL is there primarily so that two distinct ;;; conses will be present, namely the entry and the cdr of the entry. ;;; These are used internal as catch tags, the first for RETURN and the ;;; second for RESTART. If the NIL has been clobbered to be INVALID, ;;; then the block has been exited, and a return to that block is an error. ;;; ;;; Each entry in GENV looks like (TAG MARKER . BODY), where TAG is ;;; a go tag, MARKER is a unique cons used as a catch tag, and BODY ;;; is the statement sequence that follows the go tag. If the car of ;;; MARKER, normally NIL, has been clobbered to be INVALID, then ;;; the tag body has been exited, and a go to that tag is an error. ;;; An interpreted-lexical-closure contains a function (normally a ;;; lambda-expression) and the lexical environment. (defstruct interpreted-lexical-closure function venv fenv benv genv) ;;; The EVALHOOK feature allows a user-supplied function to be called ;;; whenever a form is to be evaluated. The presence of the lexical ;;; environment requires an extension of the feature as it is defined ;;; in MacLISP. Here, the user hook function must accept not only ;;; the form to be evaluated, but also the components of the lexical ;;; environment; these must then be passed verbatim to EVALHOOK or ;;; *EVAL in order to perform the evaluation of the form correctly. ;;; The precise number of components should perhaps be allowed to be ;;; implementation-dependent, so it is probably best to require the ;;; user hook function to accept arguments as (FORM &REST ENV) and ;;; then to perform evaluation by (APPLY #'EVALHOOK FORM HOOKFN ENV), ;;; for example. (defvar evalhook nil) (defun evalhook (exp hookfn venv fenv benv genv) (let ((evalhook hookfn)) (%eval exp venv fenv benv genv))) (defun eval (exp) (*eval exp nil nil nil nil)) ;;; *EVAL looks useless here, but does more complex things ;;; in alternative implementations of this evaluator. (defun *eval (exp venv fenv benv genv) (%eval exp venv fenv benv genv)) ! ;;; Function names beginning with "%" are intended to be internal ;;; and not defined in the Common LISP white pages. ;;; %EVAL is the main evaluation function. (defun %eval (exp venv fenv benv genv) (if (not (null evalhook)) (let ((hookfn evalhook) (evalhook nil)) (funcall hookfn exp venv fenv benv genv)) (typecase exp ;; A symbol is first looked up in the lexical variable environment. (symbol (let ((slot (assoc exp venv))) (cond ((and (not (null slot)) (not (null (cdr slot)))) (cadr slot)) ((boundp exp) (symbol-value exp)) (t (cerror :unbound-variable "The symbol ~S has no value" exp))))) ;; Numbers, string, and characters self-evaluate. ((or number string character) exp) ;; Conses require elaborate treatment based on the car. (cons (typecase (car exp) ;; A symbol is first looked up in the lexical function environment. ;; This lookup is cheap if the environment is empty, a common case. (symbol (let ((fn (car exp))) (loop (let ((slot (assoc fn fenv))) (unless (null slot) (return (case (cadr slot) (macro (%eval (%macroexpand (cddr slot) (if (eq fn (car exp)) exp (cons fn (cdr exp)))))) (function (%apply (cddr slot) (%evlis (cdr exp) venv fenv benv genv))) (t ))))) ;; If not in lexical function environment, ;; try the definition cell of the symbol. (when (fboundp fn) (return (cond ((special-form-p fn) (%invoke-special-form fn (cdr exp) venv fenv benv genv)) ((macro-p fn) (%eval (%macroexpand (get-macro-function (symbol-function fn)) (if (eq fn (car exp)) exp (cons fn (cdr exp)))) venv fenv benv genv)) (t (%apply (symbol-function fn) (%evlis (cdr exp) venv fenv benv genv)))))) (setq fn (cerror :undefined-function "The symbol ~S has no function definition" fn)) (unless (symbolp fn) (return (%apply fn (%evlis (cdr exp) venv fenv benv genv))))))) ;; A cons in function position must be a lambda-expression. ;; Note that the construction of a lexical closure is avoided here. (cons (%lambda-apply (car exp) venv fenv benv genv (%evlis (cdr exp) venv fenv benv genv))) (t (%eval (cerror :invalid-form "Cannot evaluate the form ~S: function position has invalid type ~S" exp (type-of (car exp))) venv fenv benv genv)))) (t (%eval (cerror :invalid-form "Cannot evaluate the form ~S: invalid type ~S" exp (type-of exp)) venv fenv benv genv))))) ! ;;; Given a list of forms, evaluate each and return a list of results. (defun %evlis (forms venv fenv benv genv) (mapcar #'(lambda (form) (%eval form venv fenv benv genv)) forms)) ;;; Given a list of forms, evaluate each, discarding the results of ;;; all but the last, and returning all results from the last. (defun %evprogn (body venv fenv benv genv) (if (endp body) nil (do ((b body (cdr b))) ((endp (cdr b)) (%eval (car b) venv fenv benv genv)) (%eval (car b) venv fenv benv genv)))) ;;; APPLY takes a function, a number of single arguments, and finally ;;; a list of all remaining arguments. The following song and dance ;;; attempts to construct efficiently a list of all the arguments. (defun apply (fn firstarg &rest args*) (%apply fn (cond ((null args*) firstarg) ((null (cdr args*)) (cons firstarg (car args*))) (t (do ((x args* (cdr x)) (z (cddr args*) (cdr z))) ((null z) (rplacd x (cadr x)) (cons firstarg (car args*)))))))) ! ;;; %APPLY does the real work of applying a function to a list of arguments. (defun %apply (fn args) (typecase fn ;; For closures over dynamic variables, complex magic is required. (closure (with-closure-bindings-in-effect fn (%apply (closure-function fn) args))) ;; For a compiled function, an implementation-dependent "spread" ;; operation and invocation is required. (compiled-function (%invoke-compiled-function fn args)) ;; The same goes for a compiled closure over lexical variables. (compiled-lexical-closure (%invoke-compiled-lexical-closure fn args)) ;; The treatment of interpreted lexical closures is elucidated fully here. (interpreted-lexical-closure (%lambda-apply (interpreted-lexical-closure-function fn) (interpreted-lexical-closure-venv fn) (interpreted-lexical-closure-fenv fn) (interpreted-lexical-closure-benv fn) (interpreted-lexical-closure-genv fn) args)) ;; For a symbol, the function definition is used, if it is a function. (symbol (%apply (cond ((not (fboundp fn)) (cerror :undefined-function "The symbol ~S has no function definition" fn)) ((special-form-p fn) (cerror :invalid-function "The symbol ~S cannot be applied: it names a special form" fn)) ((macro-p fn) (cerror :invalid-function "The symbol ~S cannot be applied: it names a macro" fn)) (t (symbol-function fn))) args)) ;; Applying a raw lambda-expression uses the null lexical environment. (cons (if (eq (car fn) 'lambda) (%lambda-apply fn nil nil nil nil args) (%apply (cerror :invalid-function "~S is not a valid function" fn) args))) (t (%apply (cerror :invalid function "~S has an invalid type ~S for a function" fn (type-of fn)) args)))) ! ;;; %LAMBDA-APPLY is the hairy part, that takes care of applying ;;; a lambda-expression in a given lexical environment to given ;;; arguments. The complexity arises primarily from the processing ;;; of the parameter list. ;;; ;;; If at any point the lambda-expression is found to be malformed ;;; (typically because of an invalid parameter list), or if the list ;;; of arguments is not suitable for the lambda-expression, a correctable ;;; error is signalled; correction causes a throw to be performed to ;;; the tag %LAMBDA-APPLY-RETRY, passing back a (possibly new) ;;; lambda-expression and a (possibly new) list of arguments. ;;; The application is then retried. If the new lambda-expression ;;; is not really a lambda-expression, then %APPLY is used instead of ;;; %LAMBDA-APPLY. ;;; ;;; In this evaluator, PROGV is used to instantiate variable bindings ;;; (though its use is embedded with a macro called %BIND-VAR). ;;; The throw that precedes a retry will cause special bindings to ;;; be popped before the retry. (defun %lambda-apply (lexp venv fenv benv genv args) (multiple-value-bind (newfn newargs) (catch '%lambda-apply-retry (return-from %lambda-apply (%lambda-apply-1 lexp venv fenv benv genv args))) (if (and (consp lexp) (eq (car lexp) 'lambda)) (%lambda-apply newfn venv fenv benv genv newargs) (%apply newfn newargs)))) ;;; Calling this function will unwind all special variables ;;; and cause FN to be applied to ARGS in the original lexical ;;; and dynamic environment in force when %LAMBDA-APPLY was called. (defun %lambda-apply-retry (fn args) (throw '%lambda-apply-retry (values fn args))) ;;; This function is convenient when the lambda expression is found ;;; to be malformed. REASON should be a string explaining the problem. (defun %bad-lambda-exp (lexp oldargs reason) (%lambda-apply-retry (cerror :invalid-function "Improperly formed lambda-expression ~S: ~A" lexp reason) oldargs)) ;;; (%BIND-VAR VAR VALUE . BODY) evaluates VAR to produce a symbol name ;;; and VALUE to produce a value. If VAR is determined to have been ;;; declared special (as indicated by the current binding of the variable ;;; SPECIALS, which should be a list of symbols, or by a SPECIAL property), ;;; then a special binding is established using PROGV. Otherwise an ;;; entry is pushed onto the a-list presumed to be in the variable VENV. (defmacro %bind-var (var value &body body) `(let ((var ,var) (value ,value)) (let ((specp (or (member var specials) (get var 'special)))) (progv (and specp (list var)) (and specp (list value)) (push (if specp (list var) (list var value)) venv) ,@body)))) ;;; %LAMBDA-KEYWORD-P is true iff X (which must be a symbol) ;;; has a name beginning with an ampersand. (defun %lambda-keyword-p (x) (char= #\& (char 0 (symbol-pname x)))) ! ;;; %LAMBDA-APPLY-1 is responsible for verifying that LEXP is ;;; a lambda-expression, for extracting a list of all variables ;;; declared SPECIAL in DECLARE forms, and for finding the ;;; body that follows any DECLARE forms. (defun %lambda-apply-1 (lexp venv fenv benv genv args) (cond ((or (not (consp lexp)) (not (eq (car lexp) 'lambda)) (atom (cdr lexp)) (not (listp (cadr lexp)))) (%bad-lambda-exp lexp args "improper lambda or lambda-list")) (t (do ((body (cddr lexp) (cdr body)) (specials '())) ((or (endp body) (not (listp (car body))) (not (eq (caar body) 'declare))) (%bind-required lexp args (cadr lexp) venv fenv benv genv venv args specials body)) (dolist (decl (cdar body)) (when (eq (car decl) 'special) (setq specials (if (null specials) ;Avoid consing (cdar decl) (append (cdar decl) specials))))))))) ;;; %BIND-REQUIRED handles the pairing of arguments to required parameters. ;;; Error checking is performed for too few or too many arguments. ;;; If a lambda-list keyword is found, %TRY-OPTIONAL is called. ;;; Here, as elsewhere, if the binding process terminates satisfactorily ;;; then the body is evaluated using %EVPROGN in the newly constructed ;;; dynamic and lexical environment. (defun %bind-required (lexp oldargs varlist fenv benv genv venv args specials body) (cond ((endp varlist) (if (null args) (%evprogn body venv fenv benv genv) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args)))) ((not (symbolp (car varlist))) (%bad-lambda-exp lexp oldargs "required parameter name not a symbol")) ((%lambda-keyword-p (car varlist)) (%try-optional lexp oldargs varlist fenv benv genv venv args specials body)) ((null args) (%lambda-apply-retry lexp (cerror :too-few-arguments "Too few arguments for function ~S: ~S" lexp oldargs))) (t (%bind-var (car varlist) (car args) (%bind-required lexp oldargs varlist fenv benv genv venv (cdr args) specials body))))) ! ;;; %TRY-OPTIONAL determines whether the lambda-list keyword &OPTIONAL ;;; has been found. If so, optional parameters are processed; if not, ;;; the buck is passed to %TRY-REST. (defun %try-optional (lexp oldargs varlist fenv benv genv venv args specials body) (cond ((eq (car varlist) '&optional) (%bind-optional lexp oldargs (cdr varlist) fenv benv genv venv args specials body)) (t (%try-rest lexp oldargs varlist fenv benv genv venv args specials body)))) ;;; %BIND-OPTIONAL determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-optional (lexp oldargs varlist fenv benv genv venv args specials body) (cond ((endp varlist) (if (null args) (%evprogn body venv fenv benv genv) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args)))) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (%try-rest lexp oldargs varlist fenv benv genv venv args specials body) (%process-optional lexp oldargs varlist fenv benv genv venv args specials body varspec nil nil))) ((and (consp varspec) (symbolp (car varspec)) (listp (cdr varspec)) (or (endp (cddr varspec)) (and (symbolp (caddr varspec)) (not (endp (caddr varspec))) (endp (cdddr varspec))))) (%process-optional lexp oldargs varlist fenv benv genv venv args specials body (car varspec) (cadr varspec) (caddr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed optional parameter specifier"))))))) ;;; %PROCESS-OPTIONAL takes care of binding the parameter, ;;; and also the supplied-p variable, if any. (defun %process-optional (lexp oldargs varlist fenv benv genv venv args specials body var init varp) (let ((value (if (null args) (%eval init venv fenv benv genv) (car args)))) (%bind-var var value (if varp (%bind-var varp (not (null args)) (%bind-optional lexp oldargs varlist fenv benv genv venv args specials body)) (%bind-optional lexp oldargs varlist fenv benv genv venv args specials body))))) ! ;;; %TRY-REST determines whether the lambda-list keyword &REST ;;; has been found. If so, the rest parameter is processed; ;;; if not, the buck is passed to %TRY-KEY, after a check for ;;; too many arguments. (defun %try-rest (lexp oldargs varlist fenv benv genv venv args specials body) (cond ((eq (car varlist) '&rest) (%bind-rest lexp oldargs (cdr varlist) fenv benv genv venv args specials body)) ((and (not (eq (car varlist) '&key)) (not (null args))) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args))) (t (%try-key lexp oldargs varlist fenv benv genv venv args specials body)))) ;;; %BIND-REST ensures that there is a parameter specifier for ;;; the &REST parameter, binds it, and then evaluates the body or ;;; calls %TRY-KEY. (defun %bind-rest (lexp oldargs varlist fenv benv genv venv args specials body) (cond ((or (endp varlist) (not (symbolp (car varlist)))) (%bad-lambda-exp lexp oldargs "missing rest parameter specifier")) (t (%bind-var (car varlist) args (cond ((endp (cdr varlist)) (%evprogn body venv fenv benv genv)) ((and (symbolp (cadr varlist)) (%lambda-keyword-p (cadr varlist))) (%try-key lexp oldargs varlist fenv benv genv venv args specials body)) (t (%bad-lambda-exp lexp oldargs "malformed after rest parameter specifier"))))))) ! ;;; %TRY-KEY determines whether the lambda-list keyword &KEY ;;; has been found. If so, keyword parameters are processed; ;;; if not, the buck is passed to %TRY-AUX. (defun %try-key (lexp oldargs varlist fenv benv genv venv args specials body) (cond ((eq (car varlist) '&key) (%bind-key lexp oldargs (cdr varlist) fenv benv genv venv args specials body nil)) (t (%try-aux lexp oldargs varlist fenv benv genv venv specials body)))) ;;; %BIND-KEY determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-key (lexp oldargs varlist fenv benv genv venv args specials body keys) (cond ((endp varlist) ;; Optional error check for bad keywords. (do ((a args (cddr a))) ((endp args)) (unless (member (car a) keys) (cerror :unexpected-keyword "Keyword not expected by function ~S: ~S" lexp (car a)))) (%evprogn body venv fenv benv genv)) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (cond ((not (eq varspec '&allow-other-keywords)) (%try-aux lexp oldargs varlist fenv benv genv venv specials body)) ((endp (cdr varlist)) (%evprogn body venv fenv benv genv)) ((%lambda-keyword-p (cadr varlist)) (%try-aux lexp oldargs (cdr varlist) fenv benv genv venv specials body)) (t (%bad-lambda-exp lexp oldargs "invalid after &ALLOW-OTHER-KEYWORDS"))) (%process-key lexp oldargs varlist fenv benv genv venv args specials body keys (intern varspec keyword-package) varspec nil nil))) ((and (consp varspec) (or (symbolp (car varspec)) (and (consp (car varspec)) (consp (cdar varspec)) (symbolp (cadar varspec)) (endp (cddar varspec)))) (listp (cdr varspec)) (or (endp (cddr varspec)) (and (symbolp (caddr varspec)) (not (endp (caddr varspec))) (endp (cdddr varspec))))) (%process-key lexp oldargs varlist fenv benv genv venv args specials body keys (if (consp (car varspec)) (caar varspec) (intern (car varspec) keyword-package)) (if (consp (car varspec)) (cadar varspec) (car varspec)) (cadr varspec) (caddr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed keyword parameter specifier"))))))) ;;; %PROCESS-KEY takes care of binding the parameter, ;;; and also the supplied-p variable, if any. (defun %process-key (lexp oldargs varlist fenv benv genv venv args specials body keys kwd var init varp) (let ((value (do ((a args (cddr a))) ((endp a) (%eval init venv fenv benv genv)) (when (eq (car a) kwd) (return (cadr a)))))) (%bind-var var value (if varp (%bind-var varp (not (null args)) (%bind-key lexp oldargs varlist fenv benv genv venv args specials body (cons kwd keys))) (%bind-key lexp oldargs varlist fenv benv genv venv args specials body (cons kwd keys)))))) ! ;;; %TRY-AUX determines whether the keyword &AUX ;;; has been found. If so, auxiliary variables are processed; ;;; if not, an error is signalled. (defun %try-aux (lexp oldargs varlist fenv benv genv venv specials body) (cond ((eq (car varlist) '&aux) (%bind-aux lexp oldargs (cdr varlist) fenv benv genv venv specials body)) (t (%bad-lambda-exp lexp oldargs "unknown or misplaced lambda-list keyword")))) ;;; %BIND-AUX determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-aux (lexp oldargs varlist fenv benv genv venv specials body) (cond ((endp varlist) (%evprogn body venv fenv benv genv)) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (%bad-lambda-exp lexp oldargs "unknown or misplaced lambda-list keyword") (%process-aux lexp oldargs varlist fenv benv genv venv specials body varspec nil))) ((and (consp varspec) (symbolp (car varspec)) (listp (cdr varspec)) (endp (cddr varspec))) (%process-aux lexp oldargs varlist fenv benv genv venv specials body (car varspec) (cadr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed aux variable specifier"))))))) ;;; %PROCESS-AUX takes care of binding the auxiliary variable. (defun %process-aux (lexp oldargs varlist fenv benv genv venv specials body var init) (%bind-var var (and init (%eval init venv fenv benv genv)) (%bind-aux lexp oldargs varlist fenv benv genv venv specials body))) ! ;;; Definitions for various special forms and macros. (defspec quote (obj) (venv fenv benv genv) obj) (defspec function (fn) (venv fenv benv genv) (cond ((consp fn) (cond ((eq (car fn) 'lambda) (make-interpreted-closure :function fn :venv venv :fenv fenv :benv benv :genv genv)) (t (cerror ???)))) ((symbolp fn) (loop (let ((slot (assoc fn fenv))) (unless (null slot) (case (cadr slot) (macro (cerror ???)) (function (return (cddr slot))) (t )))) (when (fboundp fn) (cond ((or (special-form-p fn) (macro-p fn)) (cerror ???)) (t (return (symbol-function fn))))) (setq fn (cerror :undefined-function "The symbol ~S has no function definition" fn)) (unless (symbolp fn) (return fn)))) (t (cerror ???)))) (defspec if (pred con &optional alt) (venv fenv benv genv) (if (%eval pred venv fenv benv genv) (%eval con venv fenv benv genv) (%eval alt venv fenv benv genv))) ;;; The BLOCK construct provides a PROGN with a named contour around it. ;;; It is interpreted by first putting an entry onto BENV, consisting ;;; of a 2-list of the name and NIL. This provides two unique conses ;;; for use as catch tags. Then the body is executed. ;;; If a RETURN or RESTART is interpreted, a throw occurs. If the BLOCK ;;; construct is exited for any reason (including falling off the end, which ;;; retu rns the results of evaluating the last form in the body), the NIL in ;;; the entry is clobbered to be INVALID, to indicate that that particular ;;; entry is no longer valid for RETURN or RESTART. (defspec block (name &body body) (venv fenv benv genv) (let ((slot (list name nil))) ;Use slot for return, (cdr slot) for restart (unwind-protect (catch slot (block exit (loop (catch (cdr slot) (return-from exit (%evprogn body venv fenv (cons slot benv) genv)))))) (rplaca (cdr slot) 'invalid)))) (defspec return (form) (venv fenv benv genv) (let ((slot (assoc nil benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw slot (%eval form venv fenv benv genv)))))) (defspec return-from (name form) (venv fenv benv genv) (let ((slot (assoc name benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw slot (%eval form venv fenv benv genv)))))) (defspec restart (form) (venv fenv benv genv) (let ((slot (assoc nil benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw (cdr slot) (%eval form venv fenv benv genv)))))) (defspec restart-from (name form) (venv fenv benv genv) (let ((slot (assoc name benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw (cdr slot) (%eval form venv fenv benv genv)))))) ! (defmacro prog (vars &rest body) (do ((b body (cdr b)) (decls '() (cons (car b) decls))) ((or (endp b) (atom (car b)) (not (eq (caar b) 'declare))) `(let ,vars ,@(nreverse decls) (block nil (tagbody ,@b)))))) ;;; The TAGBODY construct provides a body with GO tags in it. ;;; It is interpreted by first putting one entry onto GENV for ;;; every tag in the body; doing this ahead of time saves searching ;;; at GO time. A unique cons whose car is NIL is constructed for ;;; use as a unique catch tag. Then the body is executed. ;;; If a GO is interpreted, a throw occurs, sending as the thrown ;;; value the point in the body after the relevant tag. ;;; If the TAGBODY construct is exited for any reason (including ;;; falling off the end, which produces the value NIL), the car of ;;; the unique marker is clobbered to be INVALID, to indicate that ;;; tags associated with that marker are no longer valid. (defspec tagbody (&rest body) (venv fenv benv genv) (do ((b body (cdr b)) (marker (list nil))) ((endp p) (block exit (unwind-protect (loop (setq body (catch marker (do ((b body (cdr b))) ((endp b) (return-from exit nil)) (unless (atom (car b)) (%eval (car b) venv fenv benv genv)))))) (rplaca marker 'invalid)))) (when (atom (car b)) (push (list* (car b) marker (cdr b)) genv)))) (defspec go (tag) (venv fenv benv genv) (let ((slot (assoc tag genv))) (cond ((null slot) (ferror ???)) ((eq (caadr slot) 'invalid) (ferror ???)) (t (throw (cadr slot) (cddr slot)))))) ----------------------------------------------------------- ;;; This version uses some special variables to avoid passing stuff around. ;;; This evaluator splits the lexical environment into four ;;; logically distinct entities: ;;; VENV = lexical variable environment ;;; FENV = lexical function and macro environment ;;; BENV = block name environment ;;; GENV = go tag environment ;;; Each environment is an a-list. It is never the case that ;;; one can grow and another shrink simultaneously; the four ;;; parts could be united into a single a-list. The four-part ;;; division saves consing and search time. ;;; ;;; In this implementation, the four environment parts are normally ;;; kept in four special variables %VENV%, %FENV%, %BENV%, and %GENV%. ;;; (These are internal to the implementation, and are not meant to ;;; be user-accessible.) (defvar %venv% nil) (defvar %fenv% nil) (defvar %benv% nil) (defvar %genv% nil) ;;; Each entry in VENV has one of two forms: (VAR VALUE) or (VAR). ;;; The first indicates a lexical binding of VAR to VALUE, and the ;;; second indicates a special binding of VAR (implying that the ;;; special value should be used). ;;; ;;; Each entry in FENV looks like (NAME TYPE . FN), where NAME is the ;;; functional name, TYPE is either FUNCTION or MACRO, and FN is the ;;; function or macro-expansion function, respectively. Entries of ;;; type FUNCTION are made by FLET and LABELS; those of type MACRO ;;; are made by MACROLET. ;;; ;;; Each entry in BENV looks like (NAME NIL), where NAME is the name ;;; of the block. The NIL is there primarily so that two distinct ;;; conses will be present, namely the entry and the cdr of the entry. ;;; These are used internal as catch tags, the first for RETURN and the ;;; second for RESTART. If the NIL has been clobbered to be INVALID, ;;; then the block has been exited, and a return to that block is an error. ;;; ;;; Each entry in GENV looks like (TAG MARKER . BODY), where TAG is ;;; a go tag, MARKER is a unique cons used as a catch tag, and BODY ;;; is the statement sequence that follows the go tag. If the car of ;;; MARKER, normally NIL, has been clobbered to be INVALID, then ;;; the tag body has been exited, and a go to that tag is an error. ;;; An interpreted-lexical-closure contains a function (normally a ;;; lambda-expression) and the lexical environment. (defstruct interpreted-lexical-closure function venv fenv benv genv) ;;; The EVALHOOK feature allows a user-supplied function to be called ;;; whenever a form is to be evaluated. The presence of the lexical ;;; environment requires an extension of the feature as it is defined ;;; in MacLISP. Here, the user hook function must accept not only ;;; the form to be evaluated, but also the components of the lexical ;;; environment; these must then be passed verbatim to EVALHOOK or ;;; *EVAL in order to perform the evaluation of the form correctly. ;;; The precise number of components should perhaps be allowed to be ;;; implementation-dependent, so it is probably best to require the ;;; user hook function to accept arguments as (FORM &REST ENV) and ;;; then to perform evaluation by (APPLY #'EVALHOOK FORM HOOKFN ENV), ;;; for example. (defvar evalhook nil) (defun evalhook (exp hookfn %venv% %fenv% %benv% %genv%) (let ((evalhook hookfn)) (%eval exp))) (defun eval (exp) (*eval exp nil nil nil nil)) (defun *eval (exp %venv% %fenv% %benv% %genv%) (%eval exp)) ! ;;; Function names beginning with "%" are intended to be internal ;;; and not defined in the Common LISP white pages. ;;; %EVAL is the main evaluation function. It evaluates EXP in ;;; the current lexical environment, assumed to be in %VENV%, etc. (defun %eval (exp) (if (not (null evalhook)) (let ((hookfn evalhook) (evalhook nil)) (funcall hookfn exp %venv% %fenv% %benv% %genv%)) (typecase exp ;; A symbol is first looked up in the lexical variable environment. (symbol (let ((slot (assoc exp %venv%))) (cond ((and (not (null slot)) (not (null (cdr slot)))) (cadr slot)) ((boundp exp) (symbol-value exp)) (t (cerror :unbound-variable "The symbol ~S has no value" exp))))) ;; Numbers, string, and characters self-evaluate. ((or number string character) exp) ;; Conses require elaborate treatment based on the car. (cons (typecase (car exp) ;; A symbol is first looked up in the lexical function environment. ;; This lookup is cheap if the environment is empty, a common case. (symbol (let ((fn (car exp))) (loop (let ((slot (assoc fn %fenv%))) (unless (null slot) (return (case (cadr slot) (macro (%eval (%macroexpand (cddr slot) (if (eq fn (car exp)) exp (cons fn (cdr exp)))))) (function (%apply (cddr slot) (%evlis (cdr exp)))) (t ))))) ;; If not in lexical function environment, ;; try the definition cell of the symbol. (when (fboundp fn) (return (cond ((special-form-p fn) (%invoke-special-form fn (cdr exp))) ((macro-p fn) (%eval (%macroexpand (get-macro-function (symbol-function fn)) (if (eq fn (car exp)) exp (cons fn (cdr exp)))))) (t (%apply (symbol-function fn) (%evlis (cdr exp))))))) (setq fn (cerror :undefined-function "The symbol ~S has no function definition" fn)) (unless (symbolp fn) (return (%apply fn (%evlis (cdr exp)))))))) ;; A cons in function position must be a lambda-expression. ;; Note that the construction of a lexical closure is avoided here. (cons (%lambda-apply (car exp) (%evlis (cdr exp)))) (t (%eval (cerror :invalid-form "Cannot evaluate the form ~S: function position has invalid type ~S" exp (type-of (car exp))))))) (t (%eval (cerror :invalid-form "Cannot evaluate the form ~S: invalid type ~S" exp (type-of exp))))))) ! ;;; Given a list of forms, evaluate each and return a list of results. (defun %evlis (forms) (mapcar #'(lambda (form) (%eval form)) forms)) ;;; Given a list of forms, evaluate each, discarding the results of ;;; all but the last, and returning all results from the last. (defun %evprogn (body) (if (endp body) nil (do ((b body (cdr b))) ((endp (cdr b)) (%eval (car b))) (%eval (car b))))) ;;; APPLY takes a function, a number of single arguments, and finally ;;; a list of all remaining arguments. The following song and dance ;;; attempts to construct efficiently a list of all the arguments. (defun apply (fn firstarg &rest args*) (%apply fn (cond ((null args*) firstarg) ((null (cdr args*)) (cons firstarg (car args*))) (t (do ((x args* (cdr x)) (z (cddr args*) (cdr z))) ((null z) (rplacd x (cadr x)) (cons firstarg (car args*)))))))) ! ;;; %APPLY does the real work of applying a function to a list of arguments. (defun %apply (fn args) (typecase fn ;; For closures over dynamic variables, complex magic is required. (closure (with-closure-bindings-in-effect fn (%apply (closure-function fn) args))) ;; For a compiled function, an implementation-dependent "spread" ;; operation and invocation is required. (compiled-function (%invoke-compiled-function fn args)) ;; The same goes for a compiled closure over lexical variables. (compiled-lexical-closure (%invoke-compiled-lexical-closure fn args)) ;; The treatment of interpreted lexical closures is elucidated fully here. (interpreted-lexical-closure (let ((%venv% (interpreted-lexical-closure-venv fn)) (%fenv% (interpreted-lexical-closure-fenv fn)) (%benv% (interpreted-lexical-closure-benv fn)) (%genv% (interpreted-lexical-closure-genv fn))) (%lambda-apply (interpreted-lexical-closure-function fn) args))) ;; For a symbol, the function definition is used, if it is a function. (symbol (%apply (cond ((not (fboundp fn)) (cerror :undefined-function "The symbol ~S has no function definition" fn)) ((special-form-p fn) (cerror :invalid-function "The symbol ~S cannot be applied: it names a special form" fn)) ((macro-p fn) (cerror :invalid-function "The symbol ~S cannot be applied: it names a macro" fn)) (t (symbol-function fn))) args)) ;; Applying a raw lambda-expression uses the null lexical environment. (cons (if (eq (car fn) 'lambda) (let ((%venv% nil) (%fenv% nil) (%benv% nil) (%genv% nil)) (%lambda-apply fn args)) (%apply (cerror :invalid-function "~S is not a valid function" fn) args))) (t (%apply (cerror :invalid function "~S has an invalid type ~S for a function" fn (type-of fn)) args)))) ! ;;; %LAMBDA-APPLY is the hairy part, that takes care of applying ;;; a lambda-expression in a given lexical environment to given ;;; arguments. The complexity arises primarily from the processing ;;; of the parameter list. ;;; ;;; If at any point the lambda-expression is found to be malformed ;;; (typically because of an invalid parameter list), or if the list ;;; of arguments is not suitable for the lambda-expression, a correctable ;;; error is signalled; correction causes a throw to be performed to ;;; the tag %LAMBDA-APPLY-RETRY, passing back a (possibly new) ;;; lambda-expression and a (possibly new) list of arguments. ;;; The application is then retried. If the new lambda-expression ;;; is not really a lambda-expression, then %APPLY is used instead of ;;; %LAMBDA-APPLY. ;;; ;;; In this evaluator, PROGV is used to instantiate variable bindings ;;; (though its use is embedded with a macro called %BIND-VAR). ;;; The throw that precedes a retry will cause special bindings to ;;; be popped before the retry. (defun %lambda-apply (lexp args) (multiple-value-bind (newfn newargs) (catch '%lambda-apply-retry (return-from %lambda-apply (let ((%venv% %venv%)) (%lambda-apply-1 lexp args)))) (if (and (consp lexp) (eq (car lexp) 'lambda)) (%lambda-apply newfn newargs) (%apply newfn newargs)))) ;;; Calling this function will unwind all special variables ;;; and cause FN to be applied to ARGS in the original lexical ;;; and dynamic environment in force when %LAMBDA-APPLY was called. (defun %lambda-apply-retry (fn args) (throw '%lambda-apply-retry (values fn args))) ;;; This function is convenient when the lambda expression is found ;;; to be malformed. REASON should be a string explaining the problem. (defun %bad-lambda-exp (lexp oldargs reason) (%lambda-apply-retry (cerror :invalid-function "Improperly formed lambda-expression ~S: ~A" lexp reason) oldargs)) ;;; (%BIND-VAR VAR VALUE . BODY) evaluates VAR to produce a symbol name ;;; and VALUE to produce a value. If VAR is determined to have been ;;; declared special (as indicated by the current binding of the variable ;;; SPECIALS, which should be a list of symbols, or by a SPECIAL property), ;;; then a special binding is established using PROGV. Otherwise an ;;; entry is pushed onto the a-list presumed to be in the variable VENV. (defmacro %bind-var (var value &body body) `(let ((var ,var) (value ,value)) (let ((specp (or (member var specials) (get var 'special)))) (progv (and specp (list var)) (and specp (list value)) (push (if specp (list var) (list var value)) %venv%) ,@body)))) ;;; %LAMBDA-KEYWORD-P is true iff X (which must be a symbol) ;;; has a name beginning with an ampersand. (defun %lambda-keyword-p (x) (char= #\& (char 0 (symbol-pname x)))) ! ;;; %LAMBDA-APPLY-1 is responsible for verifying that LEXP is ;;; a lambda-expression, for extracting a list of all variables ;;; declared SPECIAL in DECLARE forms, and for finding the ;;; body that follows any DECLARE forms. (defun %lambda-apply-1 (lexp args) (cond ((or (not (consp lexp)) (not (eq (car lexp) 'lambda)) (atom (cdr lexp)) (not (listp (cadr lexp)))) (%bad-lambda-exp lexp args "improper lambda or lambda-list")) (t (do ((body (cddr lexp) (cdr body)) (specials '())) ((or (endp body) (not (listp (car body))) (not (eq (caar body) 'declare))) (%bind-required lexp args (cadr lexp) args specials body)) (dolist (decl (cdar body)) (when (eq (car decl) 'special) (setq specials (if (null specials) ;Avoid consing (cdar decl) (append (cdar decl) specials))))))))) ;;; %BIND-REQUIRED handles the pairing of arguments to required parameters. ;;; Error checking is performed for too few or too many arguments. ;;; If a lambda-list keyword is found, %TRY-OPTIONAL is called. ;;; Here, as elsewhere, if the binding process terminates satisfactorily ;;; then the body is evaluated using %EVPROGN in the newly constructed ;;; dynamic and lexical environment. (defun %bind-required (lexp oldargs varlist args specials body) (cond ((endp varlist) (if (null args) (%evprogn body) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args)))) ((not (symbolp (car varlist))) (%bad-lambda-exp lexp oldargs "required parameter name not a symbol")) ((%lambda-keyword-p (car varlist)) (%try-optional lexp oldargs varlist args specials body)) ((null args) (%lambda-apply-retry lexp (cerror :too-few-arguments "Too few arguments for function ~S: ~S" lexp oldargs))) (t (%bind-var (car varlist) (car args) (%bind-required lexp oldargs varlist (cdr args) specials body))))) ! ;;; %TRY-OPTIONAL determines whether the lambda-list keyword &OPTIONAL ;;; has been found. If so, optional parameters are processed; if not, ;;; the buck is passed to %TRY-REST. (defun %try-optional (lexp oldargs varlist args specials body) (cond ((eq (car varlist) '&optional) (%bind-optional lexp oldargs (cdr varlist) args specials body)) (t (%try-rest lexp oldargs varlist args specials body)))) ;;; %BIND-OPTIONAL determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-optional (lexp oldargs varlist args specials body) (cond ((endp varlist) (if (null args) (%evprogn body) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args)))) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (%try-rest lexp oldargs varlist args specials body) (%process-optional lexp oldargs varlist args specials body varspec nil nil))) ((and (consp varspec) (symbolp (car varspec)) (listp (cdr varspec)) (or (endp (cddr varspec)) (and (symbolp (caddr varspec)) (not (endp (caddr varspec))) (endp (cdddr varspec))))) (%process-optional lexp oldargs varlist args specials body (car varspec) (cadr varspec) (caddr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed optional parameter specifier"))))))) ;;; %PROCESS-OPTIONAL takes care of binding the parameter, ;;; and also the supplied-p variable, if any. (defun %process-optional (lexp oldargs varlist args specials body var init varp) (let ((value (if (null args) (%eval init) (car args)))) (%bind-var var value (if varp (%bind-var varp (not (null args)) (%bind-optional lexp oldargs varlist args specials body)) (%bind-optional lexp oldargs varlist args specials body))))) ! ;;; %TRY-REST determines whether the lambda-list keyword &REST ;;; has been found. If so, the rest parameter is processed; ;;; if not, the buck is passed to %TRY-KEY, after a check for ;;; too many arguments. (defun %try-rest (lexp oldargs varlist args specials body) (cond ((eq (car varlist) '&rest) (%bind-rest lexp oldargs (cdr varlist) args specials body)) ((and (not (eq (car varlist) '&key)) (not (null args))) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args))) (t (%try-key lexp oldargs varlist args specials body)))) ;;; %BIND-REST ensures that there is a parameter specifier for ;;; the &REST parameter, binds it, and then evaluates the body or ;;; calls %TRY-KEY. (defun %bind-rest (lexp oldargs varlist args specials body) (cond ((or (endp varlist) (not (symbolp (car varlist)))) (%bad-lambda-exp lexp oldargs "missing rest parameter specifier")) (t (%bind-var (car varlist) args (cond ((endp (cdr varlist)) (%evprogn body)) ((and (symbolp (cadr varlist)) (%lambda-keyword-p (cadr varlist))) (%try-key lexp oldargs varlist args specials body)) (t (%bad-lambda-exp lexp oldargs "malformed after rest parameter specifier"))))))) ! ;;; %TRY-KEY determines whether the lambda-list keyword &KEY ;;; has been found. If so, keyword parameters are processed; ;;; if not, the buck is passed to %TRY-AUX. (defun %try-key (lexp oldargs varlist args specials body) (cond ((eq (car varlist) '&key) (%bind-key lexp oldargs (cdr varlist) args specials body nil)) (t (%try-aux lexp oldargs varlist specials body)))) ;;; %BIND-KEY determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-key (lexp oldargs varlist args specials body keys) (cond ((endp varlist) ;; Optional error check for bad keywords. (do ((a args (cddr a))) ((endp args)) (unless (member (car a) keys) (cerror :unexpected-keyword "Keyword not expected by function ~S: ~S" lexp (car a)))) (%evprogn body)) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (cond ((not (eq varspec '&allow-other-keywords)) (%try-aux lexp oldargs varlist specials body)) ((endp (cdr varlist)) (%evprogn body)) ((%lambda-keyword-p (cadr varlist)) (%try-aux lexp oldargs (cdr varlist) specials body)) (t (%bad-lambda-exp lexp oldargs "invalid after &ALLOW-OTHER-KEYWORDS"))) (%process-key lexp oldargs varlist args specials body keys (intern varspec keyword-package) varspec nil nil))) ((and (consp varspec) (or (symbolp (car varspec)) (and (consp (car varspec)) (consp (cdar varspec)) (symbolp (cadar varspec)) (endp (cddar varspec)))) (listp (cdr varspec)) (or (endp (cddr varspec)) (and (symbolp (caddr varspec)) (not (endp (caddr varspec))) (endp (cdddr varspec))))) (%process-key lexp oldargs varlist args specials body keys (if (consp (car varspec)) (caar varspec) (intern (car varspec) keyword-package)) (if (consp (car varspec)) (cadar varspec) (car varspec)) (cadr varspec) (caddr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed keyword parameter specifier"))))))) ;;; %PROCESS-KEY takes care of binding the parameter, ;;; and also the supplied-p variable, if any. (defun %process-key (lexp oldargs varlist args specials body keys kwd var init varp) (let ((value (do ((a args (cddr a))) ((endp a) (%eval init)) (when (eq (car a) kwd) (return (cadr a)))))) (%bind-var var value (if varp (%bind-var varp (not (null args)) (%bind-key lexp oldargs varlist args specials body (cons kwd keys))) (%bind-key lexp oldargs varlist args specials body (cons kwd keys)))))) ! ;;; %TRY-AUX determines whether the keyword &AUX ;;; has been found. If so, auxiliary variables are processed; ;;; if not, an error is signalled. (defun %try-aux (lexp oldargs varlist specials body) (cond ((eq (car varlist) '&aux) (%bind-aux lexp oldargs (cdr varlist) specials body)) (t (%bad-lambda-exp lexp oldargs "unknown or misplaced lambda-list keyword")))) ;;; %BIND-AUX determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-aux (lexp oldargs varlist specials body) (cond ((endp varlist) (%evprogn body)) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (%bad-lambda-exp lexp oldargs "unknown or misplaced lambda-list keyword") (%process-aux lexp oldargs varlist specials body varspec nil))) ((and (consp varspec) (symbolp (car varspec)) (listp (cdr varspec)) (endp (cddr varspec))) (%process-aux lexp oldargs varlist specials body (car varspec) (cadr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed aux variable specifier"))))))) ;;; %PROCESS-AUX takes care of binding the auxiliary variable. (defun %process-aux (lexp oldargs varlist specials body var init) (%bind-var var (and init (%eval init)) (%bind-aux lexp oldargs varlist specials body))) ! ;;; Definitions for various special forms and macros. (defspec quote (obj) obj) (defspec function (fn) (cond ((consp fn) (cond ((eq (car fn) 'lambda) (make-interpreted-closure :function fn :venv %venv% :fenv %fenv% :benv %benv% :genv %genv%)) (t (cerror ???)))) ((symbolp fn) (loop (let ((slot (assoc fn %fenv%))) (unless (null slot) (case (cadr slot) (macro (cerror ???)) (function (return (cddr slot))) (t )))) (when (fboundp fn) (cond ((or (special-form-p fn) (macro-p fn)) (cerror ???)) (t (return (symbol-function fn))))) (setq fn (cerror :undefined-function "The symbol ~S has no function definition" fn)) (unless (symbolp fn) (return fn)))) (t (cerror ???)))) (defspec if (pred con &optional alt) (if (%eval pred) (%eval con) (%eval alt))) ;;; The BLOCK construct provides a PROGN with a named contour around it. ;;; It is interpreted by first putting an entry onto BENV, consisting ;;; of a 2-list of the name and NIL. This provides two unique conses ;;; for use as catch tags. Then the body is executed. ;;; If a RETURN or RESTART is interpreted, a throw occurs. If the BLOCK ;;; construct is exited for any reason (including falling off the end, which ;;; retu rns the results of evaluating the last form in the body), the NIL in ;;; the entry is clobbered to be INVALID, to indicate that that particular ;;; entry is no longer valid for RETURN or RESTART. (defspec block (name &body body) (let ((slot (list name nil))) ;Use slot for return, (cdr slot) for restart (unwind-protect (catch slot (block exit (loop (catch (cdr slot) (return-from exit (let ((%benv% (cons slot %benv%))) (%evprogn body))))))) (rplaca (cdr slot) 'invalid)))) (defspec return (form) (let ((slot (assoc nil %benv%))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw slot (%eval form)))))) (defspec return-from (name form) (let ((slot (assoc name %benv%))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw slot (%eval form)))))) (defspec restart (form) (let ((slot (assoc nil %benv%))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw (cdr slot) (%eval form)))))) (defspec restart-from (name form) (let ((slot (assoc name %benv%))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw (cdr slot) (%eval form)))))) ! (defmacro prog (vars &rest body) (do ((b body (cdr b)) (decls '() (cons (car b) decls))) ((or (endp b) (atom (car b)) (not (eq (caar b) 'declare))) `(let ,vars ,@(nreverse decls) (block nil (tagbody ,@b)))))) ;;; The TAGBODY construct provides a body with GO tags in it. ;;; It is interpreted by first putting one entry onto GENV for ;;; every tag in the body; doing this ahead of time saves searching ;;; at GO time. A unique cons whose car is NIL is constructed for ;;; use as a unique catch tag. Then the body is executed. ;;; If a GO is interpreted, a throw occurs, sending as the thrown ;;; value the point in the body after the relevant tag. ;;; If the TAGBODY construct is exited for any reason (including ;;; falling off the end, which produces the value NIL), the car of ;;; the unique marker is clobbered to be INVALID, to indicate that ;;; tags associated with that marker are no longer valid. (defspec tagbody (&rest body) (let ((%genv% %genv%)) (do ((b body (cdr b)) (marker (list nil))) ((endp p) (block exit (unwind-protect (loop (setq body (catch marker (do ((b body (cdr b))) ((endp b) (return-from exit nil)) (unless (atom (car b)) (%eval (car b))))))) (rplaca marker 'invalid)))) (when (atom (car b)) (push (list* (car b) marker (cdr b)) %genv%))))) (defspec go (tag) (let ((slot (assoc tag %genv%))) (cond ((null slot) (ferror ???)) ((eq (caadr slot) 'invalid) (ferror ???)) (t (throw (cadr slot) (cddr slot)))))) -------  Date: Sunday, 26 September 1982 22:58-EDT From: Scott E. Fahlman To: Kent M. Pitman Cc: common-lisp at SU-AI Subject: Reply to KMP I disagree with KMP's analysis of what VECTOR should do in the "RPG memorial" proposal. I think I confused him by talking about "implementations that do not support vectors". He seems to believe that VREF, CHAR, and BIT would not work in such implementations. That was not my intent. Perhaps the right way to look at it is to say that EVERY Common Lisp implementation supports vectors. In some implementations (notably Zetalisp) vectors and 1-D arrays are the same thing; in other implementations (including Vax and Spice Lisp) vectors are a restricted subset of 1-D arrays. VREF works only on vectors (it is equivalent to AREF with a VECTOR declaration). That means that in Zetalisp, VREF would work on every 1-D array, and VECTORP would be true for every 1-D array. BIT-VECTORS and STRINGS would likewise be identical to BIT-SEQUENCES and CHAR-SEQUENCES in Zetalisp. I don't think this is backwards. I think that simple strings have to self-eval. If it were up to me, all arrays would self-eval, but this was voted down because it was felt that it provided too little error checking. I don't care whether general char-sequences (or complex strings, whatever) self-eval or not, but I think the Zetalisp folks would like the complex and simple strings to behave pretty much the same. I don't think that we want bit-vectors to self-eval unless every vector does. Similarly, I think EQUAL has to go down into strings; it should not go down into bit-sequences unless it goes down into every vector. There are no user-visible array leaders in Common Lisp. If an implementation wants to provide user-visible additions to the Common Lisp data structures (array leaders or property lists on strings or whatever) it is up to that implementation to describe how these things interact with the built-in features; all that is required is that legal Common Lisp code run without modification. I would suggest that EQUAL and EQUALP not descend into such things, but it is really none of Common lisp's business. -- Scott  Date: 25 September 1982 1016-EDT (Saturday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: KMP's remarks on arrays Recall, as a point of fact, that array leaders have been removed from Common LISP as a user-visible feature. --Guy  Date: 25 September 1982 06:39-EDT From: Kent M. Pitman Subject: Arrays and Vectors To: Fahlman at CMU-20C cc: common-lisp at SU-AI I am in agreement with much of the "RPG memorial" proposal. A few comments on the few parts that left me feeling uneasy... ----- Date: Thursday, 23 September 1982 00:38-EDT From: Scott E. Fahlman Re: Arrays and vectors (again) ... If an implementation does not support vectors, the :VECTOR keyword is ignored except that the error is still signalled on inconsistent cases; The additional restrictions on vectors are not enforced. MAKE-VECTOR is treated just like the equivalent make-array. VECTORP is true of every 1-D array, STRINGP of every CHAR-SEQUENCE, and BIT-VECTOR of every BIT-SEQUENCE. ----- If an implementation DOES support vectors, does VECTORP return true for all 1-D arrays? If not, I think you have this backwards. If an implementation doesn't support vectors, VECTORP, STRINGP, and BIT-VECTOR should always return NIL... I think this answers DLW's point about strings wanting to not be vectors. In his system, vectors will not exist, so he may write: (COND ((VECTORP foo) (PRIMITIVE-THAT-DOESNT-WORK-ON-REAL-VECTORS foo)) ...) and though erroneous, it would run in his implementation. He wouldn't find out that it that primitive wouldn't work on real vectors until he ported his code to other systems. Further, he can't write (COND ((VECTORP foo) ...code for sites with vectors...) (T ...code for things that wouldnt' be vectors at other sites...)) because the things that wouldn't be vectors at other sites are still vectors on his machine which doesn't claim to support vectors. The right thing for sites that don't have vectors is to make them punt and always use the fully generic operators. You'll never get to code that calls hairy generic stuff if you have VECTORP lie and say everything is simple! You want it to lie and say everything is not. ----- CHAR-SEQUENCEs, including strings, self-eval; all other arrays cause an error when passed to EVAL... ----- In thinking about this, I'm relatively convinced that the exact set of things which want to self-eval are those things which are intended to be typed in. The reason is that other things just don't tend to wind up in evaluable positions. I can't imagine how I could ever get (PRINT ) very easily and I'm inclined to think it's an error. It's easy to see how (PRINT ) can happen, but that's not going to cause the hairy-string to be EVAL'd, so it doesn't matter. Seems like it'd be worth the error checking to make only strings self-eval. ----- EQUAL descends into CHAR-SEQUENCEs, but not into any other arrays. ----- I would argue that this also follows from the fact that the contents of this kind of array are always visible. In this sense, this satisfies the novice's heuristic about EQUAL that says if two things print the same, they are probably EQUAL. I suspect that the same reasoning says that BIT-SEQUENCEs should also be descended by EQUAL. It should probably be made clear that EQUAL descends only the main data area of the arrays it descends and not the array leader. Indeed, I assume that a string (having no array leader) can be equal to a CHARACTER-SEQUENCE which has one if the main data areas are the same? In that case, does the fill-pointer looked at in the complex case? I assume so. That should also be made explicit in the documentation. ----- EQUALP descends into arrays of all kinds, comparing the corresponding elements with EQUALP. EQUALP is false if the array dimensions are not the same, but it is not sensitive to the element-type of the array, whether it is a vector, etc. In comparing the dimensions of vectors, EQUALP uses the length from 0 to the fill pointer; it does not look at any elements beyond the fill pointer. ----- Again, I take it that it doesn't descend array leaders?  Date: Thursday, 23 September 1982 12:14-EDT From: Scott E. Fahlman To: DLW at MIT-MC Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) The "RPG memorial" proposal contains almost exactly the same machinery as the "simple-switch" proposal; only the names have been changed to protect the simple. With regard to strings, the difference is that asking for a "string" gets you what was previously called a simple string -- no fill pointer. You can still get a string-like object with a fill pointer, but you have to get it via MAKE-ARRAY. The "string" functions still work on it, and it still prints out with the double-quote syntax. On read-in of a "..." form, you end up with a simple string, but everyone agreed to that earlier, I believe. It would be very awkward, and not too useful, to have a printing syntax that preserved the fill pointer and the characters beyond it. While I agree that "strings with fill pointers" are essential things to have around, I think that they are needed in relatively few places, so a name-change to favor the more common simple case should not be too difficult to live with. Am I missing something here? -- Scott  Date: Thursday, 23 September 1982 12:14-EDT From: Scott E. Fahlman To: DLW at MIT-MC Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) The "RPG memorial" proposal contains almost exactly the same machinery as the "simple-switch" proposal; only the names have been changed to protect the simple. With regard to strings, the difference is that asking for a "string" gets you what was previously called a simple string -- no fill pointer. You can still get a string-like object with a fill pointer, but you have to get it via MAKE-ARRAY. The "string" functions still work on it, and it still prints out with the double-quote syntax. On read-in of a "..." form, you end up with a simple string, but everyone agreed to that earlier, I believe. It would be very awkward, and not too useful, to have a printing syntax that preserved the fill pointer and the characters beyond it. While I agree that "strings with fill pointers" are essential things to have around, I think that they are needed in relatively few places, so a name-change to favor the more common simple case should not be too difficult to live with. Am I missing something here? -- Scott  Date: Thursday, 23 September 1982 09:49-EDT From: Leonard N. Zubkoff To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) My vote goes for the new "RPG memorial" proposal. I think the name assignments are far more reasonable in this version. Leonard  Date: Thursday, 23 September 1982 09:49-EDT From: Leonard N. Zubkoff To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) My vote goes for the new "RPG memorial" proposal. I think the name assignments are far more reasonable in this version. Leonard  Date: Thursday, 23 September 1982 07:48-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Arrays and vectors (again) In your latest ("RPG memorial") proposal, strings are vectors, and so if I write a program that creates a string with a fill pointer, it may not work in some Common Lisp implementations. This has been my main objection to most of the earlier proposals. Strings with fill pointers are extremely useful. -------  Date: Thursday, 23 September 1982 00:38-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Arrays and vectors (again) Several people have stated that they dislike my earlier proposal because it uses the good names (VECTOR, STRING, BIT-VECTOR, VREF, CHAR, BIT) on general 1-D arrays, and makes the user say "simple" when he wants one of the more specialized high-efficiency versions. This makes extra work for users, who will want simple vectors at least 95% of the time. In addition, there is the argument that simple vectors should be thought of as a first-class data-type (in implementations that provide them) and not as a mere degenerate form of array. Just to see what it looks like, I have re-worked the earlier proposal to give the good names to the simple forms. This does not really eliminate any of the classes in the earlier proposal, since each of those classes had some attributes or operations that distinguished it from the others. Since there are getting to be a lot of proposals around, we need some nomencalture for future discussions. My first attempt, with the user-settable :PRINT option should be called the "print-switch" proposal; the next one, with the heavy use of the :SIMPLE switch should be the "simple-switch" proposal; this one can be called the "RPG memorial" proposal. Let me know what you think about this vs. the simple-switch version -- I can live with either, but I really would like to nail this down pretty soon so that we can get on with the implementation. ********************************************************************** Arrays can be 1-D or multi-D. All arrays can be created by MAKE-ARRAY and can be accessed with AREF. Storage is done via SETF of an AREF. 1-D arrays are special, in that they are also of type SEQUENCE, and can be referenced by ELT. Also, only 1-D arrays can have fill pointers. Some implementations may provide a special, highly efficient representation for simple 1-D arrays, which will be of type VECTOR. A vector is 1-dimensional, cannot have a fill pointer, cannot be displaced, and cannot be altered in size after its creation. To get a vector, you use the :VECTOR keyword to MAKE-ARRAY with a non-null value. If there are any conflicting options specified, an error is signalled. The MAKE-VECTOR form is equivalent to MAKE-ARRAY with :VECTOR T. A STRING is a VECTOR whose element-type (specified by the :ELEMENT-TYPE keyword) is STRING-CHAR. Strings are special in that they print using the "..." syntax, and they are legal inputs to a class of "string functions". Actually, these functions accept any 1-D array whose element type is STRING-CHAR. This more general class is called a CHAR-SEQUENCE. A BIT-VECTOR is a VECTOR whose element-type is BIT, alias (MOD 2). Bit-vectors are special in that they print using the #*... syntax, and they are legal inputs to a class of boolean bit-vector functions. Actually, these functions accept any 1-D array whose element-type is BIT. This more general class is called a BIT-SEQUENCE. All arrays can be referenced via AREF, but in some implementations additional efficiency can be obtained by declaring certain objects to be vectors, strings, or bit-vectors. This can be done by normal type-declarations or by special accessing forms. The form (VREF v n) is equivalent to (AREF (THE VECTOR v) n). The form (CHAR s n) is equivalent to (AREF (THE STRING s) n). The form (BIT b n) is equivalent to (AREF (THE BIT-VECTOR b) n). If an implementation does not support vectors, the :VECTOR keyword is ignored except that the error is still signalled on inconsistent cases; The additional restrictions on vectors are not enforced. MAKE-VECTOR is treated just like the equivalent make-array. VECTORP is true of every 1-D array, STRINGP of every CHAR-SEQUENCE, and BIT-VECTOR of every BIT-SEQUENCE. CHAR-SEQUENCEs, including strings, self-eval; all other arrays cause an error when passed to EVAL. EQUAL descends into CHAR-SEQUENCEs, but not into any other arrays. EQUALP descends into arrays of all kinds, comparing the corresponding elements with EQUALP. EQUALP is false if the array dimensions are not the same, but it is not sensitive to the element-type of the array, whether it is a vector, etc. In comparing the dimensions of vectors, EQUALP uses the length from 0 to the fill pointer; it does not look at any elements beyond the fill pointer. The set of type-specifiers required for all of this is ARRAY, VECTOR, STRING, BIT-VECTOR, SEQUENCE, CHAR-SEQUENCE, and BIT-SEQUENCE. Each of these has a corresponding type-P predicate, and each can be specified in list from, along with the element-type and dimension(s). MAKE-ARRAY takes the following keywords: :ELEMENT-TYPE, :INITIAL-VALUE, :INITIAL-CONTENTS, :FILL-POINTER, :DISPLACED-TO, :DISPLACED-INDEX-OFFSET, and :VECTOR. The following functions are redundant, but should be retained for clarity and emphasis in code: MAKE-VECTOR, MAKE-STRING, MAKE-BIT-VECTOR. MAKE-VECTOR takes a single length argument, along with :ELEMENT-TYPE, :INITIAL-VALUE, and :INITIAL-CONTENTS. MAKE-STRING and MAKE-BIT-VECTOR are like MAKE-VECTOR, but do not take the :ELEMENT-TYPE keyword, since the element-type is implicit. If the :VECTOR keyword is not specified to MAKE-ARRAY or related forms, the default is NIL. However, sequences produced by random forms such as CONCATENATE are vectors. Strings always are printed using the "..." syntax. Bit-vectors always are printed using the #*... syntax. Other vectors always print using the #(...) syntax. Note that in the latter case, any element-type restriction is lost upon readin, since this form always produces a vector of type T when it is read. However, the new vector will be EQUALP to the old one. The #(...) syntax observes PRINLEVEL, PRINLENGTH, and SUPPRESS-ARRAY-PRINTING. The latter switch, if non-NIL, causes the array to print in a non-readable form: #. CHAR-SEQUENCEs print out as though they were strings, using the "..." syntax. BIT-SEQUENCES print out as BIT-STRINGS, using the #*... syntax. All other arrays print out using the #nA(...) syntax, where n is the number of dimensions and the list is actually a list of lists of lists, nested n levels deep. The array elements appear at the lowest level. The #A syntax also observes PRINLEVEL, PRINLENGTH, and SUPPRESS-ARRAY-PRINTING. The #A format reads in as a non-displaced array of element-type T. Note that when an array is printed and read back in, the new version is EQUALP to the original, but some information about the original is lost: whether the original was a vector or not, element type restrictions, whether the array was displaced, whether there was a fill pointer, and the identity of any elements beyond the fill-pointer. This choice was made to favor ease of interactive use; if the user really wants to preserve in printed form some complex data structure containing more complex arrays, he will have to develop his own print format and printer.  Date: Tuesday, 21 September 1982, 16:25-EDT From: David A. Moon Subject: Indented Strings To: Scott E.Fahlman Cc: Kent M.Pitman , common-lisp at SU-AI In-reply-to: The message of 21 Sep 82 12:31-EDT from Scott E.Fahlman Date: Tuesday, 21 September 1982 12:31-EDT From: Scott E. Fahlman For many uses of the auto-fill function, readtime/compile-time is the wrong time to do the filling. What if we just create this new function, and then state that all documentation strings are run through STRING-FILL (or whatever) either when they are stashed away or when printed by DESCRIBE. I like this a lot better. How do you break paragraphs (preventing auto-filling between them), when white space at the beginning of a line is discarded. Presumably a blank line. I agree that it should be kept simple and make no claims of being a text justifier, just a "kludge" to make documentation and error messages come out readably. We should resist mightily the temptation to put in a special character that turns off the flushing of white space at the beginning of a line. In systems with windows the filling has to be done at the time that it is printed. This is also true of systems where the font can be changed and systems that support more than one width of terminal. I think this covers all proposed Common Lisp implementations. Thus the function is not a string operation, but a stream operation. In fact a string operation might be useful sometimes too, although this might be better handled by making WITH-OUTPUT-TO-STRING accept keyword options to tell it what line-length to assume, and then using the stream operation.  Date: Tuesday, 21 September 1982, 16:36-EDT From: Daniel L. Weinreb Subject: LEXICAL declarations To: common-lisp at su-ai I think Common Lisp should have a "lexical" declaration that is analogous to the "special" declaration, mainly for symmetry, also because it is occasionally useful.  Date: Tuesday, 21 September 1982, 16:25-EDT From: David A. Moon Subject: Indented Strings To: Scott E.Fahlman Cc: Kent M.Pitman , common-lisp at SU-AI In-reply-to: The message of 21 Sep 82 12:31-EDT from Scott E.Fahlman Date: Tuesday, 21 September 1982 12:31-EDT From: Scott E. Fahlman For many uses of the auto-fill function, readtime/compile-time is the wrong time to do the filling. What if we just create this new function, and then state that all documentation strings are run through STRING-FILL (or whatever) either when they are stashed away or when printed by DESCRIBE. I like this a lot better. How do you break paragraphs (preventing auto-filling between them), when white space at the beginning of a line is discarded. Presumably a blank line. I agree that it should be kept simple and make no claims of being a text justifier, just a "kludge" to make documentation and error messages come out readably. We should resist mightily the temptation to put in a special character that turns off the flushing of white space at the beginning of a line. In systems with windows the filling has to be done at the time that it is printed. This is also true of systems where the font can be changed and systems that support more than one width of terminal. I think this covers all proposed Common Lisp implementations. Thus the function is not a string operation, but a stream operation. In fact a string operation might be useful sometimes too, although this might be better handled by making WITH-OUTPUT-TO-STRING accept keyword options to tell it what line-length to assume, and then using the stream operation.  Date: Tuesday, 21 September 1982, 16:25-EDT From: David A. Moon Subject: Indented Strings To: Scott E.Fahlman Cc: Kent M.Pitman , common-lisp at SU-AI In-reply-to: The message of 21 Sep 82 12:31-EDT from Scott E.Fahlman Date: Tuesday, 21 September 1982 12:31-EDT From: Scott E. Fahlman For many uses of the auto-fill function, readtime/compile-time is the wrong time to do the filling. What if we just create this new function, and then state that all documentation strings are run through STRING-FILL (or whatever) either when they are stashed away or when printed by DESCRIBE. I like this a lot better. How do you break paragraphs (preventing auto-filling between them), when white space at the beginning of a line is discarded. Presumably a blank line. I agree that it should be kept simple and make no claims of being a text justifier, just a "kludge" to make documentation and error messages come out readably. We should resist mightily the temptation to put in a special character that turns off the flushing of white space at the beginning of a line. In systems with windows the filling has to be done at the time that it is printed. This is also true of systems where the font can be changed and systems that support more than one width of terminal. I think this covers all proposed Common Lisp implementations. Thus the function is not a string operation, but a stream operation. In fact a string operation might be useful sometimes too, although this might be better handled by making WITH-OUTPUT-TO-STRING accept keyword options to tell it what line-length to assume, and then using the stream operation.  Date: Tuesday, 21 September 1982, 16:12-EDT From: David A. Moon Subject: Hash table functions not all there To: Andy Freeman Cc: common-lisp at SU-AI In-reply-to: The message of 21 Sep 82 14:09-EDT from Andy Freeman I agree with you. Could you (or someone else) send in a concrete proposal for what is missing? For reference, here is a list of the interesting messages to hash tables in the Lisp machine. I have censored internal messages and messages that all objects handle. Clearly not all the operations you request are here, although ones for finding out the size (both allocated and in-use) are. (:CHOOSE-NEW-SIZE :CLEAR-HASH :COPY-HASH :FILLED-ELEMENTS :GET-HASH :GROW :MAP-HASH :NEW-ARRAY :NEXT-ELEMENT :PUT-HASH :REM-HASH :SIZE :SWAP-HASH)  Date: 21 Sep 1982 1109-PDT From: Andy Freeman Subject: Hash table functions To: common-lisp at SU-AI The only way to find the number of entries in a hash table is to count the number of times that the function arg to maphash is called. Since the system has to know this number anyway, even if as a computation of the number of entries until rehash, the current size and the threshold, the maphash hack isn't desirable. (If hash tables are going to be used for large link tables in a discrimination net, you have to be able to determine the number of entries while deleting links.) It is impossible to determine the current size of a hash table. The user has very little control over a hash table that has been created. There is no way to shrink it or change its :rehash-threshold/size. Is this intentional? (Many applications use tables in distinct phases, modification and access, and should be able to take advantage of this.) -andy -------  Date: 21 Sep 1982 1109-PDT From: Andy Freeman Subject: Hash table functions To: common-lisp at SU-AI The only way to find the number of entries in a hash table is to count the number of times that the function arg to maphash is called. Since the system has to know this number anyway, even if as a computation of the number of entries until rehash, the current size and the threshold, the maphash hack isn't desirable. (If hash tables are going to be used for large link tables in a discrimination net, you have to be able to determine the number of entries while deleting links.) It is impossible to determine the current size of a hash table. The user has very little control over a hash table that has been created. There is no way to shrink it or change its :rehash-threshold/size. Is this intentional? (Many applications use tables in distinct phases, modification and access, and should be able to take advantage of this.) -andy -------  Date: Tuesday, 21 September 1982 06:28-EDT From: DLW at SCRC-TENEX To: Earl A. Killian Cc: Common-Lisp at SU-AI Subject: declarations In-reply-to: The message of 20 Sep 1982 16:35-EDT from Earl A. Killian No, documentation is not the same thing as declarations. I think you are overreacting if you read my mail as meaning that Symbolics Common Lisp is going to throw away all declarations. I was not talking about any particular implementation. The Common Lisp spec, unless I am quite mistaken, is explicit in saying that implementations are allowed to discard all declarations except SPECIAL declarations. I don't know about you, but I am hoping that there will be other implementations of Common Lisp someday than the ones that are already under way now, and this is getting to be a moby big language; part of the idea of declarations was that an implementation need not bother with them if it doesn't want to, and this is the sort of thing that will help people from being discouraged before they start a new implementation.  Date: Tuesday, 21 September 1982 12:31-EDT From: Scott E. Fahlman To: Kent M. Pitman Cc: common-lisp at SU-AI Subject: Indented Strings KMP has recently proposed (I don't think this went out to the whole list) that instead of having #"..." be a back door into format, it should do an auto-fill on the string at readtime, removing any whitespace following a newline. Presumably the auto-fill would need to look at global variables (defaulted by each implementation) to determine what the fill-column, word-separators, and newline sequence ought to be. This is getting closer to what we really want for documentation strings, I think, and it is definitely better than having ~% all over the place. In fact, such an auto-fill function seems to me like something we might want to build into the language as a function -- it is at least as useful as STRING-CAPITALIZE. In addition to documentation strings, it would be useful in error messages and for all sorts of interactive user-prompting stuff. It is very hard for someone writing portable code on a Lisp machine to have stuff come out looking nice on a 24x80 screen -- runtime formatting would help a lot. We want to keep this fairly simple, though, and not reinvent Scribe or TEX. I am still not sure that we want to use #"..." as shorthand for this. For many uses of the auto-fill function, readtime/compile-time is the wrong time to do the filling. What if we just create this new function, and then state that all documentation strings are run through STRING-FILL (or whatever) either when they are stashed away or when printed by DESCRIBE. -- Scott  Date: Monday, 20 September 1982 20:10-EDT From: MOON at SCRC-TENEX To: Common-Lisp at sail Subject: Bit vectors I only just noticed that the Boolean operations on bit-vectors (BIT-AND and so forth) are non-destructive operations; they return a new bit-vector containing the result. This makes bit-vectors completely redundant with integers, which also have non-destructive mapped Boolean operations (LOGAND and so forth), except for the way they print and possibly some benefit to be derived from using generic sequence operations on them. I had always assumed that the big feature of bit vectors was the efficiency to be gained by using destructive operations, in applications such as parallel intersection and union of sets, e.g. in compiler flow analysis. I would like to propose that Common Lisp either provide destructive bit-vector operations, which store into their first argument (possibly in addition to the non-destructive ones), or else that bit vectors be removed from the language as an unuseful redundancy.  Date: 20 September 1982 1605-EDT (Monday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: Getting the type of a variable There is a difference between the interpreter knowing the type of a variable and interpreted user code being able to get that information. The interpreter, like the compiler, is a meta-program. Type declarations are intended as meta-information; they are about the user program, not part of it. That is why an implementation is allowed to ignore it. (Unfortunately, SPECIAL declarations are part of the program, for they affect its semantics.) CL presently only guarantees equivalent compiled and interpreted semantics for correct programs, that is, programs that never do anything that "is an error". It "is an error" for a variable to take on a value not of a type compatible with any type declaration for that variable.  Date: 20 September 1982 1335-pdt From: Earl A. Killian Subject: declarations To: DLW at SCRC-TENEX at MIT-MC cc: Common-Lisp at SU-AI I think that you're overreacting. First, an implementation that really wants to ignore variable declarations can have VAR-TYPE return T. Second, you don't object to there being a standard way to access the documentation string of a variable, do you? Is a declaration really all that different? It is a form of documentation... KMP was asking for concrete examples of VAR-TYPE use; the most compelling example to me is simply me typing it at the debugger (or some code in the debugger that does it automatically for me). Also, I think Symbolics will be doing its users a disservice if it chooses to ignore variable declartions. Given your current hardware, there may not being any efficiency to be gained, but efficiency is only one reason for declarations. Another reason, probably more important, is that they help catch errors. For example, JMB once made a typo and typed ESC ESC setq tab-equivalent CR to Multics EMACS, which set tab-equivalent to (), which promptly blew everything away. You can argue that there are better ways to handle this, but checking assertions on variables is a nice general mechanism that can catch many errors; it has all the good properties of making code read-only.  Date: 20 September 1982 1335-pdt From: Earl A. Killian Subject: declarations To: DLW at SCRC-TENEX at MIT-MC cc: Common-Lisp at SU-AI I think that you're overreacting. First, an implementation that really wants to ignore variable declarations can have VAR-TYPE return T. Second, you don't object to there being a standard way to access the documentation string of a variable, do you? Is a declaration really all that different? It is a form of documentation... KMP was asking for concrete examples of VAR-TYPE use; the most compelling example to me is simply me typing it at the debugger (or some code in the debugger that does it automatically for me). Also, I think Symbolics will be doing its users a disservice if it chooses to ignore variable declartions. Given your current hardware, there may not being any efficiency to be gained, but efficiency is only one reason for declarations. Another reason, probably more important, is that they help catch errors. For example, JMB once made a typo and typed ESC ESC setq tab-equivalent CR to Multics EMACS, which set tab-equivalent to (), which promptly blew everything away. You can argue that there are better ways to handle this, but checking assertions on variables is a nice general mechanism that can catch many errors; it has all the good properties of making code read-only.  Date: 20 September 1982 14:51-EDT From: Kent M. Pitman Subject: VAR-TYPE To: RPG at SU-AI cc: Common-Lisp at SU-AI I'm inclined to agree with DLW that asking the type of a variable is a bad idea. I cannot think of any reasonable example of a place where VAR-TYPE could be used in the way you think you are advocating. Can you please make up and offer for inspection a small piece of code which you think uses the proposed VAR-TYPE primitive in a way you expect it might typically be used? Concrete examples would help a lot. -kmp  Date: 20 Sep 1982 1039-PDT From: Dick Gabriel Subject: Declarations and Ignorance To: common-lisp at SU-AI From DLW: There should NOT be a way to ask the type of a variable! CL is not a typed-variable language. Implementations are EXPLCITLY ALLOWED to ignore all type declarations. This statement seems hastily spoken: if CL has as a goal guaranteeing that compiled-code semantics are the same as interpretted-code semantics, and if stock hardware is allowed declarations for the compiler, then it seems that the interpreter has to be able to obtain the type of a variable. That is, unless CL is one of those languages whose machine is allowed to consult an oracle on certain questions. If you want to EXPLICITLY ALLOW an implementation to IGNORE *ALL* type declarations, then the way that asks of a variable, what type are you?, can simply be the constant function #'(LAMBDA (X) 'POINTER).  Date: 20 Sep 1982 1031-PDT From: Dick Gabriel Subject: Vectors and Arrays (Reprise) To: common-lisp at SU-AI I would like to concur with Brooks and KMP that perhaps what ought to be called `vectors' should be what Scott calls `simple vectors'. -rpg-  Date: Monday, 20 September 1982 09:49-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Earl A. Killian Cc: common-lisp at SU-AI, STEELE at CMU-20C Subject: Proposed evaluator for Common LISP -- declarations There should NOT be a way to ask the type of a variable! CL is not a typed-variable language. Implementations are EXPLCITLY ALLOWED to ignore all type declarations. -------  Date: Monday, 20 September 1982 09:48-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC to: COMMON-LISP at SU-AI Subject: Minor changes to proposed reader syntax KMP's reasoning about use of #* sounds right; I think we should do this. As for the use of #"...", I don't think I like it. However, we might consider the simpler proposal that has been discussed, namely that #"..." reads a string but ignores all whitespace after returns. This is less ugly than having those tildes and addresses the basic problem, without making any of the problems that Moon pointed out. -------  Date: 20 September 1982 01:46-EDT From: Alan Bawden Subject: RETURN in BLOCK and PROG To: common-lisp at SU-AI Date: Monday, 20 September 1982 01:14-EDT From: RMS For example, saying that RETURN is supposed to ignore named blocks would force a choice between two unpleasant alternatives: 1) named PROG makes two blocks, a named one and an unnamed, which makes it unanalogous to BLOCK, or 2) many uses of RETURN must be changed. Alternative 1) is what I had in mind when I made the proposal. The idea is to flush the misfeature of LispMachine Lisp where you cannot write a macro that intoduces a block named FOO without also introducing a block named NIL which then keeps the user from using RETURN inside the macro's body. Thus I regard it as a win that named PROG and BLOCK will not be analogous. This doesn't shaft the LispMachine users in the littlest bit since named PROG and named DO continue to function in exactly the same way as they always have.  Date: Monday, 20 September 1982 01:14-EDT Sender: RMS at MIT-OZ From: RMS at MIT-MC To: common-lisp at sail It is fine with me if Common Lisp has only named BLOCK, and not named PROG or named DO. But named PROG and named DO exist on the Lisp machine, and I think it would be a hassle for me and the users to get rid of them. This disagreement is no problem by itself; those constructs can still exist and not be part of Common Lisp. But I do not want to see other changes "required" for Common Lisp which would screw up the handling of named PROG and DO, or be incompatible with their existence. For example, saying that RETURN is supposed to ignore named blocks would force a choice between two unpleasant alternatives: 1) named PROG makes two blocks, a named one and an unnamed, which makes it unanalogous to BLOCK, or 2) many uses of RETURN must be changed. Can't we please keep down the number of changes that are not VITAL? I will have to implement every last one of them, and I have lots of work to do as it is. Adding a new feature is not very hard, de-advertising an old feature from the manual is not very hard, but changing what an existing feature does is a real pain. -------  Date: Sunday, 19 September 1982 22:31-EDT From: Scott E. Fahlman To: Common-Lisp at SU-AI Subject: Minor changes to proposed reader syntax I basically agree with MOON on the #" business. I like #* (or whatever) better than *" for bit-vectors, but oppose KMP's alternative proposal for the #" syntax. FORMAT is tolerable in its place, but we sure don't want it to spread. I also oppose the suggestion that #n* be used to specify a radix for reading the digits of the bit-vector. This is certain to cause confusion due to the digits being read left to right -- this was all beaten to death once before. -- Scott  Date: Sunday, 19 September 1982, 02:25-EDT From: Richard M. Stallman Subject: MEMBER and ASSOC vs EQL To: common-lisp at su-ai I claim that it would be a mistake to change MEMBER and ASSOC to use EQL. Absolutely every single time that MEMBER or ASSOC is used in the Lisp machine system, and probably most uses in user code, they are used specifically because they user wanted to compare lists. (Where numbers are being compared, they are usually fixnums, so the user would still use MEMQ or ASSQ.) If MEMBER and ASSOC were changed to use EQL, every single use of them would cease to work and have to be changed. One might as well delete these functions as change them so grossly, since their primary reason for existence is compatibility with ancient tradition. In fact, it would be better to eliminate them entirely from Common Lisp. Then users would not actually need to change their code (since the Lisp machine would still support them) unless the code was to be made portable. In that case, they would change the code to use a generic sequence function, which is merely what they would have to do anyway if MEMBER and ASSOC are changed.  Date: 19 September 1982 19:45-EDT From: Kent M. Pitman To: MOON at MIT-MC cc: Common-Lisp at SU-AI Naturally I know #"..." only handles the FORMAT subset which takes no args and is only shorthand for #.(FORMAT NIL string). I just think that's a worthwhile piece of shorthand which I've had need for frequently. I often sit around trying to think up shorter strings just because the one I want doesn't fit in the space allotted. #.(FORMAT NIL ...) is just too clumsy. My earlier comments about losing information on READ had to do with losing semantic information, not syntactic information. The information loss on reading (defun f () #"This is a return value~%with two lines") is on par with the information loss of reading (defun f () #o177) in a base-10 reader, or reading (defun f () '((a . (b c)) (b . (c d)))). No semantic content is lost, only syntactic sugar. Whereas, reading something like (defun f (x) #+maclisp (g x) (h x)) on the LispM loses semantic content that cannot be recovered. Hence, I don't consider my views on these two issues to be inconsistent. I am completely baffled by your remark about using up the ~ char in a way that may not be obvious. No one should use #"..." if they don't want to put ~'s in their string. Those who do, will know what to expect.  Date: 19 September 1982 19:45-EDT From: Kent M. Pitman To: MOON at MIT-MC cc: Common-Lisp at SU-AI Naturally I know #"..." only handles the FORMAT subset which takes no args and is only shorthand for #.(FORMAT NIL string). I just think that's a worthwhile piece of shorthand which I've had need for frequently. I often sit around trying to think up shorter strings just because the one I want doesn't fit in the space allotted. #.(FORMAT NIL ...) is just too clumsy. My earlier comments about losing information on READ had to do with losing semantic information, not syntactic information. The information loss on reading (defun f () #"This is a return value~%with two lines") is on par with the information loss of reading (defun f () #o177) in a base-10 reader, or reading (defun f () '((a . (b c)) (b . (c d)))). No semantic content is lost, only syntactic sugar. Whereas, reading something like (defun f (x) #+maclisp (g x) (h x)) on the LispM loses semantic content that cannot be recovered. Hence, I don't consider my views on these two issues to be inconsistent. I am completely baffled by your remark about using up the ~ char in a way that may not be obvious. No one should use #"..." if they don't want to put ~'s in their string. Those who do, will know what to expect.  Date: Sunday, 19 September 1982, 18:47-EDT From: David A. Moon Subject: Minor changes to proposed reader syntax To: COMMON-LISP at SU-AI Cc: bsg at SCRC-TENEX at MIT-MC, acw at SCRC-TENEX at MIT-MC, jek at SCRC-TENEX at MIT-MC In-reply-to: The message of 19 Sep 82 03:33-EDT from Kent M.Pitman , The message of 14 Sep 82 01:05-EDT from David A. Moon , The message of 14 Sep 82 09:25-EDT from Bernard S Greenberg , The message of 13 Sep 82 19:13-EDT from Daniel L. Weinreb , The message of 13 Sep 82 18:15-EDT from Allan C. Wechsler I agree with KMP that bit vectors should not use #", even though I think his proposed alternate use for #" is grotesque and definitely should not be adopted. #* would be fine for bit-vectors. It should terminate on any token-separator; finding an undelimited constituent character that is not a valid digit should be an error. Some of the reasons why I don't like the #" proposal: - it only deals with carriage returns, not with any other special characters you might want to insert into a string. You cannot access FORMAT's ~C operator since you cannot supply any FORMAT arguments. - it uses up the ~ character in a way that may not be obvious. - it's mere syntatic sugar for what you can do easily enough with #. anyway. I'm also surprised that KMP, who in an earlier message advocated eliminating syntax that disappears on read-in (so that he doesn't have to write a special reader for his programmer's-assistant system), is advocating this. Oh well, I'm not always consistent either.  Date: 19 September 1982 1322-EDT (Sunday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: Reply to msg by ALAN about PROG Have you considered that instead of: (defmacro prog (vars &rest body) `(let ,vars (block nil (tagbody ,@body)))) We might actually want: (defmacro prog (vars &rest body) `(block nil (let ,vars (tagbody ,@body)))) The question being what does (prog ((a (return))) ...) mean? That is a good question. I did it the other way because I didn't want (RESTART) to flush the variable bindings. Obviously what we do about RESTART will affect this.  Date: 19 September 1982 03:38-EDT From: Kent M. Pitman To: Moon at SCRC-TENEX cc: COMMON-LISP at SU-AI Date: Sunday, 19 September 1982 00:02-EDT From: MOON at SCRC-TENEX ... What about a macro that wants to expand into both a declaration and some code, perhaps initializations of variables? Or do such macros always take a body and expand into a LET? ----- I thought some about this. Basically, inits were the only things I could think of that had any business being in the expansion with declarations. But most things that let declarations happen are of one of two classes -- applicative (eg, LAMBDA) or compositional (eg, LET). In the former case, inits would just clobber some incoming value as in (LAMBDA (X) (SETQ X 3) ...) which seems silly. In the latter case, they'd be unneeded because rather than (LET (X) (INIT-FIXNUM X 3) ...) you'd want to write (LET ((X 3)) (DECLARE-FIXNUM X) ...). I'm willing to say that macros in this position must expand into either declaration or code but not both. This also saves you from people who write useful macros that are not usable in arbitrary places because they do gratuitous declarations that force them to go at the head of a lambda contour.  Date: 19 September 1982 03:33-EDT From: Kent M. Pitman Subject: Minor changes to proposed reader syntax To: COMMON-LISP at SU-AI I propose that bit vectors be read by #*nnnn rather than #"nnnn" since it's a waste of a good matchfix operator "..." to use on something with so deterministic and endpoint. #*nnnn has its end clearly delimited by anything not a base-2 digit. I further propose extending #*nnn to allow #m*nnn meaning that the bit vector referred to is m times the number of digits and filled with the bits given by nnn in radix 2^m. So that #3*35 would be the same as #*011101 -- ie, the default radix would be 2 for bit vectors. This might be handy for people doing bit vectors with 4bit bytes so they could write #4*A3 meaning #*10100011. It is somewhat symmetric with #nR. I'm amenable to arguments that one should write radix rather than bitesize as in #8*35 and #16*A3. Also, the choice of * as opposed to something else is completely arbitrary. NIL used to use #* for something, I don't know if it still does. Maybe they'd prefer another character like underbar or something. I don't have any set feeling for that, I just don't want to waste "..."'s expressiveness on something so simple as bits. The reason I bring this up is that I found what I think is a really good use of #"...". Following in the line of reasoning that `... should be like '... when no comma is present, suppose #"..." were like "..." when no tilde was present, but fully defined by: (defun read-sharpsign-doublequote (instream char) (uninch char instream) (format nil (read instream))) so that something calling READ could not tell if the user had written, for example, #"ABC~%DEF" or "ABC DEF". This #"..." has applications to indenting code nicely without requiring runtime calls to FORMAT. Here's a case under discussion: (defun f (x) "This is a doc string which looks nice only when indented but which has the problem that later when I do (GET-DOC 'F) I find that the second and third line of the string retain the indentation." x) Could be re-written as (defun f (x) #"This is a documentation string which does not share that bug~@ and which is indented nicely." x) This also makes it possible to consider Moon's last remark about putting the doc string in the comment because it could be then indented nicely as in (defun f (x) (declare (documentation #"This is a documentation string which is ~ indented quite a lot, but which still~@ looks nice later on when retrieved because ~ it is reformatted nicely at read time.")) x) This note does not mean to make any proposals related to declare or documentation strings. I was only using this as an exmaple. Here's another example that shows how worthwhile it is -- I used to write code from time to time that said: (defun g (x) (h #.(format nil "This is a string to type out~%on two lines."))) Sometimes I'd even leave out the #. and let the string get recomputed at runtime. But now I could just write: (defun g (x) (h #"This is a string to type out~%on two lines.")) Does anyone buy this?  Date: Sunday, 19 September 1982 02:52-EDT From: MOON at SCRC-TENEX To: common-lisp at SU-AI Subject: Printing Arrays In-reply-to: The message of 19 Sep 1982 01:08-EDT from Richard M. Stallman This is pretty reasonable. If the extreme cases are provided for by suitable values of array-prinlevel and array-prinlength (0 and infinity, with NIL accepted as a synonym for infinity), we can satisfy everyone.  Date: 19 September 1982 01:55-EDT From: Richard M. Stallman Subject: case To: common-lisp at SU-AI I really do not want to have to go through all the code of the Lisp machine system and convert everything to lower case. Really really. There is a considerable community of users who like all their code in upper case (except for comments and strings), and the editor has a special feature to make it easy for them (Electric Shift Lock Mode). Most of the system is written in this style.  Date: 19 September 1982 01:55-EDT From: Richard M. Stallman Subject: case To: common-lisp at SU-AI I really do not want to have to go through all the code of the Lisp machine system and convert everything to lower case. Really really. There is a considerable community of users who like all their code in upper case (except for comments and strings), and the editor has a special feature to make it easy for them (Electric Shift Lock Mode). Most of the system is written in this style.  Date: 19 September 1982 01:08-EDT From: Richard M. Stallman Subject: Printing Arrays To: Fahlman at CMU-20C cc: common-lisp at SU-AI Rather than have each array say whether to print its elements, let the user decide after he has seen the arrays print in a brief format that he wants to see them again in a verbose format. Have variables array-prinlevel and array-prinlength with default values 1 and 4. This means that only one level of array prints its elements, and only the first four elements at that. Any array within those four elements is printed without mentioning any elements. Then have a function of no arguments which increments those variables suitably and returns the value of *. Suppose it is RPA (reprint array). It might increment array-prinlevel by 1 and array-prinlength by 100. After you see a value that was or included an array, just do (RPA) and you get to see it again with more detail. Meanwhile, programs doing explicit printing can use the same variables to control exactly what goes on.  Date: Sunday, 19 September 1982 00:07-EDT From: MOON at SCRC-TENEX To: common-lisp at SU-AI Subject: Indirect arrays In-reply-to: The message of 16 Sep 1982 1557-EDT () from Guy.Steele at CMU-10A There are other uses for indirect arrays besides those in Guy's message. For what they are worth: (4) Making a subsequence of an array manipulable as an array (a first-class object rather than a triplet of array,start,end), while retaining sharing of side-effects on the elements, when not implementing FORTRAN nor PL/I. (5) Making an array of n-bit bytes look like an array of m-bit bytes. For the n-dimension/1-dimension case, rather than making specific kludges for the particular cases that happened to be thought of first (MAPARRAY and RAVEL), I would prefer to put in a general AREF-like function for accessing n-dimensional arrays as if they were 1-dimensional, and its corresponding SETF-er. These already exist in the Lisp machine, but I won't tell you their names, since the names are gross.  Date: Sunday, 19 September 1982 00:02-EDT From: MOON at SCRC-TENEX To: COMMON-LISP at SU-AI Subject: Declarations from macros In-reply-to: The message of 16 Sep 1982 23:39-EDT from Kent M. Pitman Kent's idea of having macros able to expand into declarations is probably a good idea. Places that look for declarations are probably all going to expand macros eventually anyway. Documentation strings should be treated the same as declarations. It might even be all right to replace "naked" documentation strings with declarations. What about a macro that wants to expand into both a declaration and some code, perhaps initializations of variables? Or do such macros always take a body and expand into a LET?  Date: 18 September 1982 18:55-EDT From: Earl A. Killian Subject: declarations To: STEELE at CMU-20C cc: common-lisp at SU-AI On the question of (declare (special x)) at top-level vs. imbedded, perhaps the right thing to do is to leave the top-level one named "special", and invent a new name for the imbedded one, such as dynamic. (declare (dynamic x)) at top-level would be nop because it's non-pervasive like var declarations in general, whereas (declare (special x)) would be prevasive as before. Not clear whether (declare (special x)) should be defined imbedded or not. The Maclisp unspecial doesn't exist; is this intentional?  Date: 18 September 1982 18:46-EDT From: Earl A. Killian Subject: Proposed evaluator for Common LISP -- declarations To: STEELE at CMU-20C cc: common-lisp at SU-AI Excuse me for one part of the last message; of course there's a way to declare the type of a dynamic variable -- just put the declaration at top-level (I don't know where my mind was at the time). However there still needs to be a way to ask the type of a variable (dynamic or otherwise). This is not the same as the type of the value of the variable, of course. This will be useful for the evaluator as well as for debugging. How about a special form called var-type, such that (let ((x 1)) (declare (type integer x)) (var-type x)) returns INTEGER. Also, it's annoying that a common case will be (declare (type integer foo) (special foo)) Is there any support for a declaration that combines the above? Also, I understand that Maclisp compatibility may be important enough to warrent the hack whereby (declare (special x)) has different semantics at top-level than when imbedded, but should it's use be encouraged in new programs? Perhaps there ought to be (declare (super-special x)) for new programs? Or perhaps pervasive-special? Unfortunately, it is a loss to make this name that long.  Date: 18 September 1982 18:21-EDT From: Earl A. Killian Subject: Proposed evaluator for Common LISP -- declarations To: STEELE at CMU-20C cc: common-lisp at SU-AI Your proposed evaluator ignores type declarations. I really think it should store them and check them. Also, it occurs to me that there is no way to declare the type of a static variable, which is a loss. Without type specific arithmetic, it's going to be necessary to declare things much more often in Common Lisp for efficiency, so the declaration facility must be complete. There should also be a way to discover the declared type of a special, if any. Since evaluating a declare is supposed to signal an error, shouldn't %lambda-apply-1 gobble up any declares that it sees, rather than passing them off to %bind-required? Also, it ought to error on illegal declarations, rather than ignoring them.  Date: 18 September 1982 05:27-EDT From: Richard M. Stallman Subject: Portable declarations To: KMP at MIT-MC cc: COMMON-LISP at SU-AI I agree that allowing macros to expand into declarations is a very good idea.  Date: 17 September 1982 2129-EDT (Friday) From: David.Dill at CMU-10A (L170DD60) To: common-lisp at SU-AI Subject: equal descending into SEQUENCES Message-Id: <17Sep82 212907 DD60@CMU-10A> As KMP notes, this was discussed at the last common lisp meeting. I didn't take notes, but I think the issue wasn't resolved because the discussion got sidetracked into what EQUALP should do and whether we should adopt T's ALIKE? function. Both of these issues seem to me to be orthogonal to the original question.  Date: 17 September 1982 21:03-EDT From: Kent M. Pitman Subject: EQUAL descending arrays To: David.Dill at CMU-10A cc: COMMON-LISP at SU-AI This idea was discussed quite a bit at the last common lisp meeting. It seems to me that we addressed the issue you bring up specifically. We discussed letting EQUALP do that and we also discussed introducing a primitive like T's ALIKE? function. I wasn't taking notes. Perhaps someone that was could briefly summarize so that we don't have to re-enact that whole discussion.  Date: 17 September 1982 2021-EDT (Friday) From: David.Dill at CMU-10A (L170DD60) To: common-lisp at SU-AI Subject: array proposal Message-Id: <17Sep82 202113 DD60@CMU-10A> The fact that EQUAL behaves differently on strings than on other vectors seems to me to be a little ugly. Why not have EQUAL descend into all sequences? This allows you to avoid descending into multi-d arrays, if that's bad for some reason, and causes the correct behavior for lists and strings, and (in my opinion) more useful behavior for other sequences, since you can always use EQL for the current effect on non-string vectors.  Date: Friday, 17 September 1982, 17:17-EDT From: Daniel L. Weinreb Subject: arrays To: Killian at MIT-MULTICS, Common-Lisp at SU-AI In-reply-to: The message of 16 Sep 82 15:09-EDT from Earl A.Killian I'm not sure about anyone else, but I didn't say anything about making them more efficient. I was only trying to talk about language semantics and perceived complexity. I actually don't think it's hard to implement them such that if you don't use them, they don't slow anything else down noticably.  Date: Friday, 17 September 1982, 17:15-EDT From: Daniel L. Weinreb Subject: Revised array proposal (long) To: Fahlman at Cmu-20c, common-lisp at SU-AI In-reply-to: The message of 16 Sep 82 23:27-EDT from Scott E.Fahlman This mostly looks very good. I am still hesitant about having arrays print out their entire contents when printed, but I don't have any particular counterproposal or complaint to make right now.  Date: Thursday, 16 September 1982 23:27-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Revised array proposal (long) Here is a revision of my array proposal, fixed up in response to some of the feedback I've received. See if you like it any better than the original. In particular, I have explictly indicated that certain redundant forms such as MAKE-VECTOR should be retained, and I have removed the :PRINT keyword, since I now believe that it causes more trouble than it is worth. A revised printing proposal appears at the end of the document. ********************************************************************** Arrays can be 1-D or multi-D. All arrays can be created by MAKE-ARRAY and can be accessed with AREF. Storage is done via SETF of an AREF. The term VECTOR refers to any array of exactly one dimension. Vectors are special, in that they are also sequences, and can be referenced by ELT. Also, only vectors can have fill pointers. Vectors can be specialized along several distinct axes. The first is by the type of the elements, as specified by the :ELEMENT-TYPE keyword to MAKE-ARRAY. A vector whose element-type is STRING-CHAR is referred to as a STRING. Strings, when they print, use the "..." syntax; they also are the legal inputs to a family of string-functions, as defined in the manual. A vector whose element-type is BIT (alias (MOD 2)), is a BIT-VECTOR. These are special because they form the set of legal inputs to the boolean bit-vector functions. (We might also want to print them in a strange way -- see below.) Some implementations may provide a special, highly efficient representation for simple vectors. A simple vector is (of course) 1-D, cannot have a fill pointer, cannot be displaced, and cannot be altered in size after its creation. To get a simple vector, you use the :SIMPLE keyword to MAKE-ARRAY with a non-null value. If there are any conflicting options specified, an error is signalled. If an implementation does not support simple vectors, this keyword/value is ignored except that the error is still signalled on inconsistent cases. We need a new set of type specifiers for simple things: SIMPLE-VECTOR, SIMPLE-STRING, and SIMPLE-BIT-VECTOR, with the corresponding type-predicate functions. Simple vectors are referenced by AREF in the usual way, but the user may use THE or DECLARE to indicate at compile-time that the argument is simple, with a corresponding increase in efficiency. Implementations that do not support simple vectors ignore the "simple" part of these declarations. Strings (simple or non-simple) self-eval; all other arrays cause an error when passed to EVAL. EQUAL descends into strings, but not into any other arrays. EQUALP descends into arrays of all kinds, comparing the corresponding elements with EQUALP. EQUALP is false if the array dimensions are not the same, but it is not sensitive to the element-type of the array, whether it is simple, etc. In comparing the dimensions of vectors, EQUALP uses the length from 0 to the fill pointer; it does not look at any elements beyond the fill pointer. The set of type-specifiers required for all of this is ARRAY, VECTOR, STRING, BIT-VECTOR, SIMPLE-VECTOR, SIMPLE-STRING, SIMPLE-BIT-VECTOR. Each of these has a corresponding type-P predicate, and each can be specified in list from, along with the element-type and dimension(s). MAKE-ARRAY takes the following keywords: :ELEMENT-TYPE, :INITIAL-VALUE, :INITIAL-CONTENTS, :FILL-POINTER, and :SIMPLE. There is still some discussion as to whether we should retain array displacement, which requires :DISPLACED-TO and :DISPLACED-INDEX-OFFSET. The following functions are redundant, but should be retained for clarity and emphasis in code: MAKE-VECTOR, MAKE-STRING, MAKE-BIT-VECTOR. MAKE-VECTOR takes the same keywords as MAKE-ARRAY, but can only take a single integer as the dimension argument. MAKE-STRING and MAKE-BIT-VECTOR are like MAKE-VECTOR, but do not take the :ELEMENT-TYPE keyword, since the element-type is implicit. Similarly, we should retain the forms VREF, CHAR, and BIT, which are identical in operation to AREF, but which declare their aray argument to be VECTOR, STRING, or BIT-VECTOR, respectively. If the :SIMPLE keyword is not specified to MAKE-ARRAY or related forms, the default is NIL. However, vectors produced by random forms such as CONCATENATE are simple, and vectors created when the reader sees #(...) or "..." are also simple. As a general rule, arrays are printed in a simple format that, upon being read back in, produces a form that is EQUALP to the original. However, some information may be lost in the printing process: element-type restrictions, whether a vector is simple, whether it has a fill pointer, whether it is displaced, and the identity of any element that lies beyond the fill pointer. This choice was made to favor ease of interactive use; if the user really wants to preserve in printed form some complex data structure containing non-simple arrays, he will have to develop his own printer. A switch, SUPPRESS-ARRAY-PRINTING, is provided for users who have lots of large arrays around and don't want to see them trying to print. If non-null, this switch causes all arrays except strings to print in a short, non-readable form that does not include the elements: #. In addition, the printing of arrays and vectors (but not of strings) is subject to PRINLEVEL and PRINLENGTH. Strings, simple or otherwise, print using the "..." syntax. Upon read-in, the "..." syntax creates a simple string. Bit-vectors, simple or otherwise, print using the #"101010..." syntax. Upon read-in, this format produces a simple bit-vector. Bit vectors do observe SUPPRESS-ARRAY-PRINTING. All other vectors print out using the #(...) syntax, observing PRINLEVEL, PRINLENGTH, and SUPPRESS-ARRAY-PRINTING. This format reads in as a simple vector of element-type T. All other arrays print out using the syntax #nA(...), where n is the number of dimensions and the list is a nest of sublists n levels deep, with the array elements at the deepest level. This form observes PRINLEVEL, PRINLENGTH, and SUPPRESS-ARRAY-PRINTING. This format reads in as an array of element-type T. Query: I am still a bit uneasy about the funny string-like syntax for bit vectors. Clearly we need some way to read these in that does not turn into a type-T vector. An alternative might be to allow #(...) to be a vector of element-type T, as it is now, but to take the #n(...) syntax to mean a vector of element-type (MOD n). A bit-vector would then be #2(1 0 1 0...) and we would have a parallel notation available for byte vectors, 32-bit word vectors, etc. The use of the #n(...) syntax to indicate the length of the vector always struck me as a bit useless anyway. One flaw in this scheme is that it does not extend to multi-D arrays. Before someone suggests it, let me say that I don't like #nAm(...), where n is the rank and m is the element-type -- it would be too hard to remember which number was which. But even with this flaw, the #n(...) syntax might be useful.  Date: 16 Sep 1982 2345-EDT From: Rodney A. Brooks Subject: Re: Revised array proposal (long) To: Fahlman at CMU-20C cc: common-lisp at SU-AI In-Reply-To: Your message of 16-Sep-82 2334-EDT I thought the idea of keeping VECTOR, VREF etc. was that they would be precisely for what you call the SIMPLE-VECTOR case. Having all of ARRAYs, VECTORs and SIMPLE-s puts the cognitive overhead up above what it was to start with. I think the types should be: ARRAY, STRING, VECTOR and maybe STRING-VECTOR where the latter is what you called SIMPLE-STRING. I've left out the BIT cases because I couldn't think of any name better than BITS for the STRING analogy. -------  Date: 17 Sep 1982 1534-EDT From: STEELE at CMU-20C Subject: Proposed evaluator for Common LISP (very long) To: common-lisp at SU-AI There follows a several-page file containing a proposed LISP definition of a Common LISP evaluator. It maintains the lexical environment as list structure, and assumes PROGV as a primitive for accomplishing special bindings (but the use of PROGV is hidden in a macro). Most of the hair is in the processing of lambda-lists. The functions tend to pass a dozen or more parameters; loops are accomplished primarily by recursion, which may not be tail-recursive because of the use of PROGV to establish a special binding for one parameter before processing the next parameter specifier. I intend soon to send out two more versions of this evaluator; one that uses special variables internally instead of passing dozens of parameters, and one that is heavily bummed for Spice LISP, and uses %BIND instead of PROGV to do special bindings. ----------------------------------------------------------- ;;; This evaluator splits the lexical environment into four ;;; logically distinct entities: ;;; VENV = lexical variable environment ;;; FENV = lexical function and macro environment ;;; BENV = block name environment ;;; GENV = go tag environment ;;; Each environment is an a-list. It is never the case that ;;; one can grow and another shrink simultaneously; the four ;;; parts could be united into a single a-list. The four-part ;;; division saves consing and search time. ;;; ;;; Each entry in VENV has one of two forms: (VAR VALUE) or (VAR). ;;; The first indicates a lexical binding of VAR to VALUE, and the ;;; second indicates a special binding of VAR (implying that the ;;; special value should be used). ;;; ;;; Each entry in FENV looks like (NAME TYPE . FN), where NAME is the ;;; functional name, TYPE is either FUNCTION or MACRO, and FN is the ;;; function or macro-expansion function, respectively. Entries of ;;; type FUNCTION are made by FLET and LABELS; those of type MACRO ;;; are made by MACROLET. ;;; ;;; Each entry in BENV looks like (NAME NIL), where NAME is the name ;;; of the block. The NIL is there primarily so that two distinct ;;; conses will be present, namely the entry and the cdr of the entry. ;;; These are used internal as catch tags, the first for RETURN and the ;;; second for RESTART. If the NIL has been clobbered to be INVALID, ;;; then the block has been exited, and a return to that block is an error. ;;; ;;; Each entry in GENV looks like (TAG MARKER . BODY), where TAG is ;;; a go tag, MARKER is a unique cons used as a catch tag, and BODY ;;; is the statement sequence that follows the go tag. If the car of ;;; MARKER, normally NIL, has been clobbered to be INVALID, then ;;; the tag body has been exited, and a go to that tag is an error. ;;; An interpreted-lexical-closure contains a function (normally a ;;; lambda-expression) and the lexical environment. (defstruct interpreted-lexical-closure function venv fenv benv genv) ;;; The EVALHOOK feature allows a user-supplied function to be called ;;; whenever a form is to be evaluated. The presence of the lexical ;;; environment requires an extension of the feature as it is defined ;;; in MacLISP. Here, the user hook function must accept not only ;;; the form to be evaluated, but also the components of the lexical ;;; environment; these must then be passed verbatim to EVALHOOK or ;;; *EVAL in order to perform the evaluation of the form correctly. ;;; The precise number of components should perhaps be allowed to be ;;; implementation-dependent, so it is probably best to require the ;;; user hook function to accept arguments as (FORM &REST ENV) and ;;; then to perform evaluation by (APPLY #'EVALHOOK FORM HOOKFN ENV), ;;; for example. (defvar evalhook nil) (defun evalhook (exp hookfn venv fenv benv genv) (let ((evalhook hookfn)) (%eval exp venv fenv benv genv))) (defun eval (exp) (%eval exp nil nil nil nil)) ;;; *EVAL looks useless here, but does more complex things ;;; in alternative implementations of this evaluator. (defun *eval (exp venv fenv benv genv) (%eval exp venv fenv benv genv)) ! ;;; Function names beginning with "%" are intended to be internal ;;; and not defined in the Common LISP white pages. ;;; %EVAL is the main evaluation function. (defun %eval (exp venv fenv benv genv) (if (not (null evalhook)) (funcall evalhook exp venv fenv benv genv) (typecase exp ;; A symbol is first looked up in the lexical variable environment. (symbol (let ((slot (assoc exp venv))) (cond ((and (not (null slot)) (not (null (cdr slot)))) (cadr slot)) ((boundp exp) (symbol-value exp)) (t (cerror :unbound-variable "The symbol ~S has no value" exp))))) ;; Numbers, string, and characters self-evaluate. ((or number string character) exp) ;; Conses require elaborate treatment based on the car. (cons (typecase (car exp) ;; A symbol is first looked up in the lexical function environment. ;; This lookup is cheap if the environment is empty, a common case. (symbol (let ((fn (car exp))) (loop (let ((slot (assoc fn fenv))) (unless (null slot) (return (case (cadr slot) (macro (%eval (%macroexpand (cddr slot) (if (eq fn (car exp)) exp (cons fn (cdr exp)))))) (function (%apply (cddr slot) (%evlis (cdr exp) venv fenv benv genv))) (t ))))) ;; If not in lexical function environment, ;; try the definition cell of the symbol. (when (fboundp fn) (return (cond ((special-form-p fn) (%invoke-special-form fn (cdr exp) venv fenv benv genv)) ((macro-p fn) (%eval (%macroexpand (get-macro-function (symbol-function fn)) (if (eq fn (car exp)) exp (cons fn (cdr exp)))) venv fenv benv genv)) (t (%apply (symbol-function fn) (%evlis (cdr exp) venv fenv benv genv)))))) (setq fn (cerror :undefined-function "The symbol ~S has no function definition" fn)) (unless (symbolp fn) (return (%apply fn (%evlis (cdr exp) venv fenv benv genv))))))) ;; A cons in function position must be a lambda-expression. ;; Note that the construction of a lexical closure is avoided here. (cons (%lambda-apply (car exp) venv fenv benv genv (%evlis (cdr exp) venv fenv benv genv))) (t (%eval (cerror :invalid-form "Cannot evaluate the form ~S: function position has invalid type ~S" exp (type-of (car exp))) venv fenv benv genv)))) (t (%eval (cerror :invalid-form "Cannot evaluate the form ~S: invalid type ~S" exp (type-of exp)) venv fenv benv genv))))) ! ;;; Given a list of forms, evaluate each and return a list of results. (defun %evlis (forms venv fenv benv genv) (mapcar #'(lambda (form) (%eval form venv fenv benv genv)) forms)) ;;; Given a list of forms, evaluate each, discarding the results of ;;; all but the last, and returning all results from the last. (defun %evprogn (body venv fenv benv genv) (if (endp body) nil (do ((b body (cdr b))) ((endp (cdr b)) (%eval (car b) venv fenv benv genv)) (%eval (car b) venv fenv benv genv)))) ;;; APPLY takes a function, a number of single arguments, and finally ;;; a list of all remaining arguments. The following song and dance ;;; attempts to construct efficiently a list of all the arguments. (defun apply (fn firstarg &rest args*) (%apply fn (cond ((null args*) firstarg) ((null (cdr args*)) (cons firstarg (car args*))) (t (do ((x args* (cdr x)) (z (cddr args*) (cdr z))) ((null z) (rplacd x (cadr x)) (cons firstarg (car args*)))))))) ! ;;; %APPLY does the real work of applying a function to a list of arguments. (defun %apply (fn args) (typecase fn ;; For closures over dynamic variables, complex magic is required. (closure (with-closure-bindings-in-effect fn (%apply (closure-function fn) args))) ;; For a compiled function, an implementation-dependent "spread" ;; operation and invocation is required. (compiled-function (%invoke-compiled-function fn args)) ;; The same goes for a compiled closure over lexical variables. (compiled-lexical-closure (%invoke-compiled-lexical-closure fn args)) ;; The treatment of interpreted lexical closures is elucidated fully here. (interpreted-lexical-closure (%lambda-apply (interpreted-lexical-closure-function fn) (interpreted-lexical-closure-venv fn) (interpreted-lexical-closure-fenv fn) (interpreted-lexical-closure-benv fn) (interpreted-lexical-closure-genv fn) args)) ;; For a symbol, the function definition is used, if it is a function. (symbol (%apply (cond ((not (fboundp fn)) (cerror :undefined-function "The symbol ~S has no function definition" fn)) ((special-form-p fn) (cerror :invalid-function "The symbol ~S cannot be applied: it names a special form" fn)) ((macro-p fn) (cerror :invalid-function "The symbol ~S cannot be applied: it names a macro" fn)) (t (symbol-function fn))) args)) ;; Applying a raw lambda-expression uses the null lexical environment. (cons (if (eq (car fn) 'lambda) (%lambda-apply fn nil nil nil nil args) (%apply (cerror :invalid-function "~S is not a valid function" fn) args))) (t (%apply (cerror :invalid function "~S has an invalid type ~S for a function" fn (type-of fn)) args)))) ! ;;; %LAMBDA-APPLY is the hairy part, that takes care of applying ;;; a lambda-expression in a given lexical environment to given ;;; arguments. The complexity arises primarily from the processing ;;; of the parameter list. ;;; ;;; If at any point the lambda-expression is found to be malformed ;;; (typically because of an invalid parameter list), or if the list ;;; of arguments is not suitable for the lambda-expression, a correctable ;;; error is signalled; correction causes a throw to be performed to ;;; the tag %LAMBDA-APPLY-RETRY, passing back a (possibly new) ;;; lambda-expression and a (possibly new) list of arguments. ;;; The application is then retried. If the new lambda-expression ;;; is not really a lambda-expression, then %APPLY is used instead of ;;; %LAMBDA-APPLY. ;;; ;;; In this evaluator, PROGV is used to instantiate variable bindings ;;; (though its use is embedded with a macro called %BIND-VAR). ;;; The throw that precedes a retry will cause special bindings to ;;; be popped before the retry. (defun %lambda-apply (lexp venv fenv benv genv args) (multiple-value-bind (newfn newargs) (catch '%lambda-apply-retry (return-from %lambda-apply (%lambda-apply-1 lexp venv fenv benv genv args))) (if (and (consp lexp) (eq (car lexp) 'lambda)) (%lambda-apply newfn venv fenv benv genv newargs) (%apply newfn newargs)))) ;;; Calling this function will unwind all special variables ;;; and cause FN to be applied to ARGS in the original lexical ;;; and dynamic environment in force when %LAMBDA-APPLY was called. (defun %lambda-apply-retry (fn args) (throw '%lambda-apply-retry (values fn args))) ;;; This function is convenient when the lambda expression is found ;;; to be malformed. REASON should be a string explaining the problem. (defun %bad-lambda-exp (lexp oldargs reason) (%lambda-apply-retry (cerror :invalid-function "Improperly formed lambda-expression ~S: ~A" lexp reason) oldargs)) ;;; (%BIND-VAR VAR VALUE . BODY) evaluates VAR to produce a symbol name ;;; and VALUE to produce a value. If VAR is determined to have been ;;; declared special (as indicated by the current binding of the variable ;;; SPECIALS, which should be a list of symbols, or by a SPECIAL property), ;;; then a special binding is established using PROGV. Otherwise an ;;; entry is pushed onto the a-list presumed to be in the variable VENV. (defmacro %bind-var (var value &body body) `(let ((var ,var) (value ,value)) (let ((specp (or (member var specials) (get var 'special)))) (progv (and specp (list var)) (and specp (list value)) (push (if specp (list var) (list var value)) venv) ,@body)))) ;;; %LAMBDA-KEYWORD-P is true iff X (which must be a symbol) ;;; has a name beginning with an ampersand. (defun %lambda-keyword-p (x) (char= #\& (char 0 (symbol-pname x)))) ! ;;; %LAMBDA-APPLY-1 is responsible for verifying that LEXP is ;;; a lambda-expression, for extracting a list of all variables ;;; declared SPECIAL in DECLARE forms, and for finding the ;;; body that follows any DECLARE forms. (defun %lambda-apply-1 (lexp venv fenv benv genv args) (cond ((or (not (consp lexp)) (not (eq (car lexp) 'lambda)) (atom (cdr lexp)) (not (listp (cadr lexp)))) (%bad-lambda-exp lexp args "improper lambda or lambda-list")) (t (do ((body (cddr lexp) (cdr body)) (specials '())) ((or (endp body) (not (listp (car body))) (not (eq (caar body) 'declare))) (%bind-required lexp args (cadr lexp) venv fenv benv genv venv args specials body)) (dolist (decl (cdar body)) (when (eq (car decl) 'special) (setq specials (if (null specials) ;Avoid consing (cdar decl) (append (cdar decl) specials))))))))) ;;; %BIND-REQUIRED handles the pairing of arguments to required parameters. ;;; Error checking is performed for too few or too many arguments. ;;; If a lambda-list keyword is found, %TRY-OPTIONAL is called. ;;; Here, as elsewhere, if the binding process terminates satisfactorily ;;; then the body is evaluated using %EVPROGN in the newly constructed ;;; dynamic and lexical environment. (defun %bind-required (lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (cond ((endp varlist) (if (null args) (%evprogn body venv fenv benv genv) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args)))) ((not (symbolp (car varlist))) (%bad-lambda-exp lexp oldargs "required parameter name not a symbol")) ((%lambda-keyword-p (car varlist)) (%try-optional lexp oldargs varlist oldvenv fenv benv genv venv args specials body)) ((null args) (%lambda-apply-retry lexp (cerror :too-few-arguments "Too few arguments for function ~S: ~S" lexp oldargs))) (t (%bind-var (car varlist) (car args) (%bind-required lexp oldargs varlist oldvenv fenv benv genv venv (cdr args) specials body))))) ! ;;; %TRY-OPTIONAL determines whether the lambda-list keyword &OPTIONAL ;;; has been found. If so, optional parameters are processed; if not, ;;; the buck is passed to %TRY-REST. (defun %try-optional (lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (cond ((eq (car varlist) '&optional) (%bind-optional lexp oldargs (cdr varlist) oldvenv fenv benv genv venv args specials body)) (t (%try-rest lexp oldargs varlist oldvenv fenv benv genv venv args specials body)))) ;;; %BIND-OPTIONAL determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-optional (lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (cond ((endp varlist) (if (null args) (%evprogn body venv fenv benv genv) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args)))) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (%try-rest lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (%process-optional lexp oldargs varlist oldvenv fenv benv genv venv args specials body varspec nil nil))) ((and (consp varspec) (symbolp (car varspec)) (listp (cdr varspec)) (or (endp (cddr varspec)) (and (symbolp (caddr varspec)) (not (endp (caddr varspec))) (endp (cdddr varspec))))) (%process-optional lexp oldargs varlist oldvenv fenv benv genv venv args specials body (car varspec) (cadr varspec) (caddr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed optional parameter specifier"))))))) ;;; %PROCESS-OPTIONAL takes care of binding the parameter, ;;; and also the supplied-p variable, if any. (defun %process-optional (lexp oldargs varlist oldvenv fenv benv genv venv args specials body var init varp) (let ((value (if (null args) (%eval init venv fenv benv genv) (car args)))) (%bind-var var value (if varp (%bind-var varp (not (null args)) (%bind-optional lexp oldargs varlist oldvenv fenv benv genv venv args specials body)) (%bind-optional lexp oldargs varlist oldvenv fenv benv genv venv args specials body))))) ! ;;; %TRY-REST determines whether the lambda-list keyword &REST ;;; has been found. If so, the rest parameter is processed; ;;; if not, the buck is passed to %TRY-KEY, after a check for ;;; too many arguments. (defun %try-rest (lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (cond ((eq (car varlist) '&rest) (%bind-rest lexp oldargs (cdr varlist) oldvenv fenv benv genv venv args specials body)) ((and (not (eq (car varlist) '&key)) (not (null args))) (%lambda-apply-retry lexp (cerror :too-many-arguments "Too many arguments for function ~S: ~S" lexp args))) (t (%try-key lexp oldargs varlist oldvenv fenv benv genv venv args specials body)))) ;;; %BIND-REST ensures that there is a parameter specifier for ;;; the &REST parameter, binds it, and then evaluates the body or ;;; calls %TRY-KEY. (defun %bind-rest (lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (cond ((or (endp varlist) (not (symbolp (car varlist)))) (%bad-lambda-exp lexp oldargs "missing rest parameter specifier")) (t (%bind-var (car varlist) args (cond ((endp (cdr varlist)) (%evprogn body venv fenv benv genv)) ((and (symbolp (cadr varlist)) (%lambda-keyword-p (cadr varlist))) (%try-key lexp oldargs varlist oldvenv fenv benv genv venv args specials body)) (t (%bad-lambda-exp lexp oldargs "malformed after rest parameter specifier"))))))) ! ;;; %TRY-KEY determines whether the lambda-list keyword &KEY ;;; has been found. If so, keyword parameters are processed; ;;; if not, the buck is passed to %TRY-AUX. (defun %try-key (lexp oldargs varlist oldvenv fenv benv genv venv args specials body) (cond ((eq (car varlist) '&key) (%bind-key lexp oldargs (cdr varlist) oldvenv fenv benv genv venv args specials body nil)) (t (%try-aux lexp oldargs varlist oldvenv fenv benv genv venv specials body)))) ;;; %BIND-KEY determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-key (lexp oldargs varlist oldvenv fenv benv genv venv args specials body keys) (cond ((endp varlist) ;; Optional error check for bad keywords. (do ((a args (cddr a))) ((endp args)) (unless (member (car a) keys) (cerror :unexpected-keyword "Keyword not expected by function ~S: ~S" lexp (car a)))) (%evprogn body venv fenv benv genv)) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (cond ((not (eq varspec '&allow-other-keywords)) (%try-aux lexp oldargs varlist oldvenv fenv benv genv venv specials body)) ((endp (cdr varlist)) (%evprogn body venv fenv benv genv)) ((%lambda-keyword-p (cadr varlist)) (%try-aux lexp oldargs (cdr varlist) oldvenv fenv benv genv venv specials body)) (t (%bad-lambda-exp lexp oldargs "invalid after &ALLOW-OTHER-KEYWORDS"))) (%process-key lexp oldargs varlist oldvenv fenv benv genv venv args specials body keys (intern varspec keyword-package) varspec nil nil))) ((and (consp varspec) (or (symbolp (car varspec)) (and (consp (car varspec)) (consp (cdar varspec)) (symbolp (cadar varspec)) (endp (cddar varspec)))) (listp (cdr varspec)) (or (endp (cddr varspec)) (and (symbolp (caddr varspec)) (not (endp (caddr varspec))) (endp (cdddr varspec))))) (%process-key lexp oldargs varlist oldvenv fenv benv genv venv args specials body keys (if (consp (car varspec)) (caar varspec) (intern (car varspec) keyword-package)) (if (consp (car varspec)) (cadar varspec) (car varspec)) (cadr varspec) (caddr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed keyword parameter specifier"))))))) ;;; %PROCESS-KEY takes care of binding the parameter, ;;; and also the supplied-p variable, if any. (defun %process-key (lexp oldargs varlist oldvenv fenv benv genv venv args specials body keys kwd var init varp) (let ((value (do ((a args (cddr a))) ((endp a) (%eval init venv fenv benv genv)) (when (eq (car a) kwd) (return (cadr a)))))) (%bind-var var value (if varp (%bind-var varp (not (null args)) (%bind-key lexp oldargs varlist oldvenv fenv benv genv venv args specials body (cons kwd keys))) (%bind-key lexp oldargs varlist oldvenv fenv benv genv venv args specials body (cons kwd keys)))))) ! ;;; %TRY-AUX determines whether the keyword &AUX ;;; has been found. If so, auxiliary variables are processed; ;;; if not, an error is signalled. (defun %try-aux (lexp oldargs varlist oldvenv fenv benv genv venv specials body) (cond ((eq (car varlist) '&aux) (%bind-aux lexp oldargs (cdr varlist) oldvenv fenv benv genv venv specials body)) (t (%bad-lambda-exp lexp oldargs "unknown or misplaced lambda-list keyword")))) ;;; %BIND-AUX determines whether the parameter list is exhausted. ;;; If not, it parses the next specifier. (defun %bind-aux (lexp oldargs varlist oldvenv fenv benv genv venv specials body) (cond ((endp varlist) (%evprogn body venv fenv benv genv)) (t (let ((varspec (car varlist))) (cond ((symbolp varspec) (if (%lambda-keyword-p varspec) (%bad-lambda-exp lexp oldargs "unknown or misplaced lambda-list keyword") (%process-aux lexp oldargs varlist oldvenv fenv benv genv venv specials body varspec nil))) ((and (consp varspec) (symbolp (car varspec)) (listp (cdr varspec)) (endp (cddr varspec))) (%process-aux lexp oldargs varlist oldvenv fenv benv genv venv specials body (car varspec) (cadr varspec))) (t (%bad-lambda-exp lexp oldargs "malformed aux variable specifier"))))))) ;;; %PROCESS-AUX takes care of binding the auxiliary variable. (defun %process-aux (lexp oldargs varlist oldvenv fenv benv genv venv specials body var init) (%bind-var var (and init (%eval init venv fenv benv genv)) (%bind-aux lexp oldargs varlist oldvenv fenv benv genv venv specials body))) ! ;;; Definitions for various special forms and macros. (defspec quote (obj) (venv fenv benv genv) obj) (defspec function (fn) (venv fenv benv genv) (cond ((consp fn) (cond ((eq (car fn) 'lambda) (make-interpreted-closure :function fn :venv venv :fenv fenv :benv benv :genv genv)) (t (cerror ???)))) ((symbolp fn) (loop (let ((slot (assoc fn fenv))) (unless (null slot) (case (cadr slot) (macro (cerror ???)) (function (return (cddr slot))) (t )))) (when (fboundp fn) (cond ((or (special-form-p fn) (macro-p fn)) (cerror ???)) (t (return (symbol-function fn))))) (setq fn (cerror :undefined-function "The symbol ~S has no function definition" fn)) (unless (symbolp fn) (return fn)))) (t (cerror ???)))) (defspec if (pred con &optional alt) (venv fenv benv genv) (if (%eval pred venv fenv benv genv) (%eval con venv fenv benv genv) (%eval alt venv fenv benv genv))) ;;; The BLOCK construct provides a PROGN with a named contour around it. ;;; It is interpreted by first putting an entry onto BENV, consisting ;;; of a 2-list of the name and NIL. This provides two unique conses ;;; for use as catch tags. Then the body is executed. ;;; If a RETURN or RESTART is interpreted, a throw occurs. If the BLOCK ;;; construct is exited for any reason (including falling off the end, which ;;; retu rns the results of evaluating the last form in the body), the NIL in ;;; the entry is clobbered to be INVALID, to indicate that that particular ;;; entry is no longer valid for RETURN or RESTART. (defspec block (name &body body) (venv fenv benv genv) (let ((slot (list name nil))) ;Use slot for return, (cdr slot) for restart (unwind-protect (catch slot (loop (catch (cdr slot) (%evprogn body venv fenv (cons slot benv) genv)))) (rplaca (cdr slot) 'invalid)))) (defspec return (form) (venv fenv benv genv) (let ((slot (assoc nil benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw slot (%eval form venv fenv benv genv)))))) (defspec return-from (name form) (venv fenv benv genv) (let ((slot (assoc name benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw slot (%eval form venv fenv benv genv)))))) (defspec restart (form) (venv fenv benv genv) (let ((slot (assoc nil benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw (cdr slot) (%eval form venv fenv benv genv)))))) (defspec restart-from (name form) (venv fenv benv genv) (let ((slot (assoc name benv))) (cond ((null slot) (ferror ???)) ((eq (cadr slot) 'invalid) (ferror ???)) (t (throw (cdr slot) (%eval form venv fenv benv genv)))))) ! (defmacro prog (vars &rest body) `(let ,vars (block nil (tagbody ,@ body)))) ;;; The TAGBODY construct provides a body with GO tags in it. ;;; It is interpreted by first putting one entry onto GENV for ;;; every tag in the body; doing this ahead of time saves searching ;;; at GO time. A unique cons whose car is NIL is constructed for ;;; use as a unique catch tag. Then the body is executed. ;;; If a GO is interpreted, a throw occurs, sending as the thrown ;;; value the point in the body after the relevant tag. ;;; If the TAGBODY construct is exited for any reason (including ;;; falling off the end, which produces the value NIL), the car of ;;; the unique marker is clobbered to be INVALID, to indicate that ;;; tags associated with that marker are no longer valid. (defspec tagbody (&rest body) (venv fenv benv genv) (do ((b body (cdr b)) (marker (list nil))) ((endp p) (block exit (unwind-protect (loop (setq body (catch marker (do ((b body (cdr b))) ((endp b) (return-from exit nil)) (unless (atom (car b)) (%eval (car b) venv fenv benv genv)))))) (rplaca marker 'invalid)))) (when (atom (car b)) (push (list* (car b) marker (cdr b)) genv)))) (defspec go (tag) (venv fenv benv genv) (let ((slot (assoc tag genv))) (cond ((null slot) (ferror ???)) ((eq (caadr slot) 'invalid) (ferror ???)) (t (throw (cadr slot) (cddr slot)))))) -------  Date: 17 September 1982 02:31-EDT From: Glenn S. Burke Subject: array proposal To: common-lisp at SU-AI I go with Rodney's interpretation, especially in terms of the accessors. I've been thinking about what it will take to do this in NIL, and what it comes down to is that it is hardly worthwhile having the specialized accessors VREF, CHAR, and BIT if they need to handle the general cases of those types of arrays. (This is unfortunate because, at least in the case of anything called a string, i would feel bad if CHAR didn't work on it. On the other hand, with kludging, discriminating between exactly two types, such as STRING and EXTEND, is easier than a dispatch, but still so gross to do inline compared to what one would get otherwise that i wouldn't do it inline. I experimented with this once.) What it comes down to is that for brevity (and interfacing to a currently stupid compiler), i am going to provide an accessing primitive for every simple type. If these are not provided in common-lisp, then they will probably have "%" as the first character of their names and be in the chartreuse pages. This has nothing to do with preserving the status-quo with what NIL already uses these names for (the simple ones, as it happens), but rather the ability to constrain the type of reference briefly, because i believe that they will be heavily used (certainly in the NIL internals they will be). Of course, the NIL-colored pages will also contain things like %string-replace, %string-translate, etc., for those who want that kind of thing. (These are the MOVC3 and MOVTC instructions.)  Date: 17 September 1982 01:07-EDT From: Kent M. Pitman To: fahlman at CMU-20C cc: Common-Lisp at SU-AI I only suggested a mechanism, not a syntax. The example I gave was only to illustrate how little the system had to support in order to leave this in the user domain. The advantage is that it would allow users to abstract their declarations in whatever way they felt most convenient. At some later time, we might standardize on someone's particular suggestions after we'd had a chance to try things out. I'm just a little concerned about prematurely selecting too much arbitrary syntax for declarations. Declarations are the sort of thing that are hard to plan for because you just never know what you're going to want to declare or on what basis you're going to want to declare it when you talk about portable code. Suppose it later becomes interesting to make certain kinds of declarations based on other kinds of conditions than just operating system or site. You'd have to introduce new kinds of arbitrary syntax, whereas my proposal by its very vagueness and generality allows for some flexibility in expanding the things one can declare without requiring a modification to the common-lisp spec.  Date: Thursday, 16 September 1982 23:49-EDT From: Scott E. Fahlman To: Kent M. Pitman Cc: COMMON-LISP at SU-AI Subject: Portable declarations KMP's proposed format for version-dependent declarations might not confuse the Lisp system, but it would confuse me. I would prefer to see a special declaration form for handling the common case of implementation-dependent declarations, rather than trying to make everything infinitely powerful and general.  Date: 16 Sep 1982 2345-EDT From: Rodney A. Brooks Subject: Re: Revised array proposal (long) To: Fahlman at CMU-20C cc: common-lisp at SU-AI In-Reply-To: Your message of 16-Sep-82 2334-EDT I thought the idea of keeping VECTOR, VREF etc. was that they would be precisely for what you call the SIMPLE-VECTOR case. Having all of ARRAYs, VECTORs and SIMPLE-s puts the cognitive overhead up above what it was to start with. I think the types should be: ARRAY, STRING, VECTOR and maybe STRING-VECTOR where the latter is what you called SIMPLE-STRING. I've left out the BIT cases because I couldn't think of any name better than BITS for the STRING analogy. -------  Date: 16 September 1982 23:39-EDT From: Kent M. Pitman Subject: Portable declarations To: COMMON-LISP at SU-AI In regards to Scott's COMPILER-VARIABLE item... (DECLARE (COMPILER-VARIABLE implementation (var1 val1) (var2 val2) ...)) ... could people comment on the idea that anything that wanted to hack compiler declarations would have to first macroexpand the first item and then check it for DECLARE-ness? This'd let you write user-code like: (DEFMACRO OPSYS-CASE (&BODY STUFF) ;doesn't error check much `(PROGN ,@(CDR (OR (ASSQ (STATUS OPSYS) STUFF) (ASSQ 'T STUFF))))) and later do (LET* ((X 3) (Y X)) (OPSYS-CASE (VAX (DECLARE (TYPE-CHECK-CAR/CDR NIL) (SPECIAL X))) (S3600 (DECLARE (SPECIAL X))) ;type checking in microcode (T (DECLARE (SPECIAL X))) ;who knows? ...stuff...) and the system would not have to have special knowledge about the macro you've written doing a DECLARE thing. It'd just macroexpand form1 in LET* and find the DECLARE there and move it as appropriate. This gives greater flexibility with respect to declarations, too, since it allows one to write declaration-writing macros. I haven't thought all the consequences fully, but at first pass I can see no problems about it other than just remembering to call MACROEXPAND in the few places where you might want to be looking for a declaration. Thoughts? -kmp  Date: Thursday, 16 September 1982 23:27-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Revised array proposal (long) Here is a revision of my array proposal, fixed up in response to some of the feedback I've received. See if you like it any better than the original. In particular, I have explictly indicated that certain redundant forms such as MAKE-VECTOR should be retained, and I have removed the :PRINT keyword, since I now believe that it causes more trouble than it is worth. A revised printing proposal appears at the end of the document. ********************************************************************** Arrays can be 1-D or multi-D. All arrays can be created by MAKE-ARRAY and can be accessed with AREF. Storage is done via SETF of an AREF. The term VECTOR refers to any array of exactly one dimension. Vectors are special, in that they are also sequences, and can be referenced by ELT. Also, only vectors can have fill pointers. Vectors can be specialized along several distinct axes. The first is by the type of the elements, as specified by the :ELEMENT-TYPE keyword to MAKE-ARRAY. A vector whose element-type is STRING-CHAR is referred to as a STRING. Strings, when they print, use the "..." syntax; they also are the legal inputs to a family of string-functions, as defined in the manual. A vector whose element-type is BIT (alias (MOD 2)), is a BIT-VECTOR. These are special because they form the set of legal inputs to the boolean bit-vector functions. (We might also want to print them in a strange way -- see below.) Some implementations may provide a special, highly efficient representation for simple vectors. A simple vector is (of course) 1-D, cannot have a fill pointer, cannot be displaced, and cannot be altered in size after its creation. To get a simple vector, you use the :SIMPLE keyword to MAKE-ARRAY with a non-null value. If there are any conflicting options specified, an error is signalled. If an implementation does not support simple vectors, this keyword/value is ignored except that the error is still signalled on inconsistent cases. We need a new set of type specifiers for simple things: SIMPLE-VECTOR, SIMPLE-STRING, and SIMPLE-BIT-VECTOR, with the corresponding type-predicate functions. Simple vectors are referenced by AREF in the usual way, but the user may use THE or DECLARE to indicate at compile-time that the argument is simple, with a corresponding increase in efficiency. Implementations that do not support simple vectors ignore the "simple" part of these declarations. Strings (simple or non-simple) self-eval; all other arrays cause an error when passed to EVAL. EQUAL descends into strings, but not into any other arrays. EQUALP descends into arrays of all kinds, comparing the corresponding elements with EQUALP. EQUALP is false if the array dimensions are not the same, but it is not sensitive to the element-type of the array, whether it is simple, etc. In comparing the dimensions of vectors, EQUALP uses the length from 0 to the fill pointer; it does not look at any elements beyond the fill pointer. The set of type-specifiers required for all of this is ARRAY, VECTOR, STRING, BIT-VECTOR, SIMPLE-VECTOR, SIMPLE-STRING, SIMPLE-BIT-VECTOR. Each of these has a corresponding type-P predicate, and each can be specified in list from, along with the element-type and dimension(s). MAKE-ARRAY takes the following keywords: :ELEMENT-TYPE, :INITIAL-VALUE, :INITIAL-CONTENTS, :FILL-POINTER, and :SIMPLE. There is still some discussion as to whether we should retain array displacement, which requires :DISPLACED-TO and :DISPLACED-INDEX-OFFSET. The following functions are redundant, but should be retained for clarity and emphasis in code: MAKE-VECTOR, MAKE-STRING, MAKE-BIT-VECTOR. MAKE-VECTOR takes the same keywords as MAKE-ARRAY, but can only take a single integer as the dimension argument. MAKE-STRING and MAKE-BIT-VECTOR are like MAKE-VECTOR, but do not take the :ELEMENT-TYPE keyword, since the element-type is implicit. Similarly, we should retain the forms VREF, CHAR, and BIT, which are identical in operation to AREF, but which declare their aray argument to be VECTOR, STRING, or BIT-VECTOR, respectively. If the :SIMPLE keyword is not specified to MAKE-ARRAY or related forms, the default is NIL. However, vectors produced by random forms such as CONCATENATE are simple, and vectors created when the reader sees #(...) or "..." are also simple. As a general rule, arrays are printed in a simple format that, upon being read back in, produces a form that is EQUALP to the original. However, some information may be lost in the printing process: element-type restrictions, whether a vector is simple, whether it has a fill pointer, whether it is displaced, and the identity of any element that lies beyond the fill pointer. This choice was made to favor ease of interactive use; if the user really wants to preserve in printed form some complex data structure containing non-simple arrays, he will have to develop his own printer. A switch, SUPPRESS-ARRAY-PRINTING, is provided for users who have lots of large arrays around and don't want to see them trying to print. If non-null, this switch causes all arrays except strings to print in a short, non-readable form that does not include the elements: #. In addition, the printing of arrays and vectors (but not of strings) is subject to PRINLEVEL and PRINLENGTH. Strings, simple or otherwise, print using the "..." syntax. Upon read-in, the "..." syntax creates a simple string. Bit-vectors, simple or otherwise, print using the #"101010..." syntax. Upon read-in, this format produces a simple bit-vector. Bit vectors do observe SUPPRESS-ARRAY-PRINTING. All other vectors print out using the #(...) syntax, observing PRINLEVEL, PRINLENGTH, and SUPPRESS-ARRAY-PRINTING. This format reads in as a simple vector of element-type T. All other arrays print out using the syntax #nA(...), where n is the number of dimensions and the list is a nest of sublists n levels deep, with the array elements at the deepest level. This form observes PRINLEVEL, PRINLENGTH, and SUPPRESS-ARRAY-PRINTING. This format reads in as an array of element-type T. Query: I am still a bit uneasy about the funny string-like syntax for bit vectors. Clearly we need some way to read these in that does not turn into a type-T vector. An alternative might be to allow #(...) to be a vector of element-type T, as it is now, but to take the #n(...) syntax to mean a vector of element-type (MOD n). A bit-vector would then be #2(1 0 1 0...) and we would have a parallel notation available for byte vectors, 32-bit word vectors, etc. The use of the #n(...) syntax to indicate the length of the vector always struck me as a bit useless anyway. One flaw in this scheme is that it does not extend to multi-D arrays. Before someone suggests it, let me say that I don't like #nAm(...), where n is the rank and m is the element-type -- it would be too hard to remember which number was which. But even with this flaw, the #n(...) syntax might be useful.  Date: 16 September 1982 1557-EDT (Thursday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: Indirect arrays I can think of three uses, offhand, for indirect arrays: (1) Making an n-d array look 1-d so you can map over it. (2) Making an n-d array look 1-d for some other purpose. (3) Simulating FORTRAN equivalence, mostly for embedding FOTRAN in LISP. Perhaps (3) should not be a goal of Common LISP. (1) and (2) could be mostly satisfied by introducing two functions MAPARRAY (takes a function and n arrays, which must all have the same rank and dimensions, and produces a new array of the same rank and dimensions), and RAVEL (produces a 1-d array with the contents of the argument array copied into it in row-major order -- note that this does not require that the argument array actually does store elements in row-major order, but if it doesn't then RAVEL will have to do some shuffling). Indeed, stock hardware atr least will probably use indirection anyway to be able to do ARRAY-GROW; but the interaction of ARRAY-FROW and user-specified indirection is very tricky. Here is a suggestion to alleviate that bad interaction: it should be an error to shrink an array that has been indirected to. This is not difficult to check, if one has a bit in the array saying whether or not it was ever indirected to. --Guy  Date: 16 September 1982 1209-pdt From: Earl A. Killian Subject: arrays To: Common-Lisp at SU-AI If the LISPM people think indirect arrays are really usefuly only for weird hacks (like accessing their bitmap), then I'm all for flushing them from Common Lisp (just to keep things clean). However, I'm not sure this makes anything more efficient, and thus rejoicing on the part of implementors may be premature. Isn't an indirection step necessary anyway to implement growing arrays (except on the LISPM where they can conditionally indirect cheaply)? I had always presumed that would be the same indirection that implemented indirect arrays. Am I confused?  Date: 16 Sep 1982 1011-PDT From: Dick Gabriel Subject: Vectors versus Arrays (concluded) To: common-lisp at SU-AI My view on vectors (reprise) is that there should at least be a series of vector functions and a description of them in the manual, and this description ought to be separate from the array description except for adjacency in the manual or other cross-referencing; this satisifies cognitive simplification (I believe). Vectors can have exactly the semantics of 1-dimensional puppy arrays - or whatever they were called - and implementations are free to implement vectors as these arrays, the implementation being trivial. If other implementations care to implement them in some other funny way for whatever reason, they may do so. Compilers can optimize on the vector operation names, on declarations, or on brilliant insight, as desired. I believe this largely revives the pre-August 21 situation with some simplifications. -rpg-  Date: Thursday, 16 September 1982 10:55-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Array Displacement I second the motion (or Nth it, I guess). Most implementations may need to create displaced arrays for internal uses, such as low-level graphics support, but such uses are almost certain to be non-portable. I would be happy to eliminate these critters from the white pages. -- Scott  Date: Thursday, 16 September 1982 10:55-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Array Displacement I second the motion (or Nth it, I guess). Most implementations may need to create displaced arrays for internal uses, such as low-level graphics support, but such uses are almost certain to be non-portable. I would be happy to eliminate these critters from the white pages. -- Scott  Date: 16-Sep-82 7:51:11 PDT (Thursday) From: Masinter at PARC-MAXC Subject: Re: #-, #+ In-reply-to: KMP's message of 16 September 1982 04:12-EDT To: COMMON-LISP at SU-AI For what it is worth, Interlisp has settled more or less on s-expression conditionalizations for environment-dependent operations; that is, there are functions (SYSTEMTYPE), (COMPILEMODE) ... which return the current values for the system you are running in, and then one writes (SELECTQ (SYSTEMTYPE) ((TENEX TOPS-20) Interlisp-10 code) (D Interlisp-D code) (VAX Interlisp-VAX code) (J Interlisp-Jericho code) etc.) Of course, the compiler can turn SYSTEMTYPE into a constant and optimizes away all but the relevant case. This means that there is no need for any special-purpose code walkers or whatever. There have been a few cases where #+ and #- would have given better control (e.g., in compiler declarations, which don't get evaluated) but for the most part, the fact that there isn't a separate syntax is an overriding advantage.  Date: Thursday, 16 September 1982 06:51-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: MOON at SCRC-TENEX Cc: common-lisp at SU-AI Subject: Hairiness of arrays I agree; array indirection could be removed from Common Lisp and probably would not severely hurt portability. Array indirection was always one of those things that was most closely "computer-like", depending on mapping one storage format on top of another storage format. It is a fundamentally poor thing to do because of the non-obvious aliasing of data elements that it sets up. It has always been one of the hardest things to document because of this. It requires the storage format of arrays to become apparent to the semantics of the language, whereas taste and sense (in my own opinion) dictate that the storage layout of an array should be no more visible than the storage layout of a symbol or a bignum. If people really want to keep this feature, we might consider restricting it to its most useful case, namely mapping a 1-D array onto a multi-D array, with the elements having exactly the same element-types. If this would make Common Lisp substantially easier to implement on some machines, and make arrays seem less daunting, I'm all for it. -------  Date: 16 Sep 1982 0513-EDT From: JoSH Subject: array hairiness To: common-lisp at SU-AI Hear, hear! I strongly urge flushing indirect arrays. --JoSH (speaking for the -20 implementation group) -------  Date: Thursday, 16 September 1982 02:23-EDT From: MOON at SCRC-TENEX to: common-lisp at SU-AI Subject: Hairiness of arrays In-reply-to: The message of 14 Sep 1982 18:23 PDT from JonL at PARC-MAXC Common Lisp arrays aren't necessarily all that hairy, frankly. The Lisp machine array leader feature was rejected as part of Common Lisp. They have fill pointers (when one-dimensional), but those are very simple to implement if you don't mind having one word of overhead. They are multi-dimensional, but the number of dimensions in an array reference can be detected at compile time, and surely needn't make the 1-dimensional case less efficient. And I think by now people know how to implement the multi-dimensional ones. They have a variety of packing types (word, byte, bit), but so do vectors. The only hairy feature Common Lisp arrays have is indirection (displacement). This isn't very important in Common Lisp, in my opinion, and I don't think I ever advocated it. It would not be a terrible idea to flush it if that makes life substantially easier for some other implementations. I have yet to see a piece of code that used NSUBSTRING and wasn't doing something wrong; NSUBSTRING is about the only Common Lisp application for array indirection, except for something, perhaps confused, going on with ADJUST-ARRAY-SIZE. (The Lisp machine needs indirect arrays, but not primarily for things that would be portable to other Common Lisp implementations).  Date: Thursday, 16 September 1982 02:23-EDT From: MOON at SCRC-TENEX to: common-lisp at SU-AI Subject: Hairiness of arrays In-reply-to: The message of 14 Sep 1982 18:23 PDT from JonL at PARC-MAXC Common Lisp arrays aren't necessarily all that hairy, frankly. The Lisp machine array leader feature was rejected as part of Common Lisp. They have fill pointers (when one-dimensional), but those are very simple to implement if you don't mind having one word of overhead. They are multi-dimensional, but the number of dimensions in an array reference can be detected at compile time, and surely needn't make the 1-dimensional case less efficient. And I think by now people know how to implement the multi-dimensional ones. They have a variety of packing types (word, byte, bit), but so do vectors. The only hairy feature Common Lisp arrays have is indirection (displacement). This isn't very important in Common Lisp, in my opinion, and I don't think I ever advocated it. It would not be a terrible idea to flush it if that makes life substantially easier for some other implementations. I have yet to see a piece of code that used NSUBSTRING and wasn't doing something wrong; NSUBSTRING is about the only Common Lisp application for array indirection, except for something, perhaps confused, going on with ADJUST-ARRAY-SIZE. (The Lisp machine needs indirect arrays, but not primarily for things that would be portable to other Common Lisp implementations).  Date: Thursday, 16 September 1982 01:02-EDT From: MOON at SCRC-TENEX to: common-lisp at SU-AI Subject: Case usage in CL manual In-reply-to: The message of 15 Sep 1982 1112-EDT () from Guy.Steele at CMU-10A Why do the Maclisp manual and the Lisp machine manual display code in lower case, although those systems actually use upper case? Well, two reasons. One is that Multics Maclisp, in keeping with the conventions of its host operating system, is a case-sensitive Lisp with the standard case being lower-case. So it would have confused a lot of readers if the manual printed code in upper-case. The other reason is that all upper-case text tends to look bad in printed manuals, particularly if you don't have a small-caps font. I didn't say anything about this earlier, because I didn't want to prolong the discussion, but I am another user who likes to see code and system output in upper case, and comments and user input in lower case, to distinguish them. Multics Maclisp, supposedly a two-case system, ended up being effectively a one-case system that never used upper case.  Date: 16 September 1982 04:12-EDT From: Kent M. Pitman To: FAHLMAN at CMU-20C cc: COMMON-LISP at SU-AI I think your fear of #+ and #- is not as irrational as you think. I certainly share it and might as well lay out my reasons why... #+ and #- were designed to handle the fact that various Lisp source files wanted to be portable but in a way that would more or less free each Lisp of knowing anything about the others. Hence, #+ and #- are designed to make it truly invisible to programs in LispM lisp (for example) that Maclisp code was interleaved within it since that Maclisp code would be semantically meaningless to any LispM program. Similarly, Maclisp didn't have to worry about LispM code. This was the best you could do without building into Maclisp assumptions about how LispM was going to do things and vice versa. Now, however, Common Lisp is trying to address portability in a different way. It is trying to make semantics predictable from dialect to dialect. As such, it can afford to use better mechanisms than #+ and #-. The bad thing about #+ and #- is that they tie a tremendously useful piece of functionality (site and feature conditionalization) to a syntax that has no underlying structure in the language. One of the nice things that people like about Lisp is the uniform underlying structure. There is no worry as there would be in Algol, for example, about the x+y is a piece of syntax which has to be treated differently than f(x,y) even though in Lisp they share a common representation. #+ and #- are quite analagous to this, I think, in that they are an irritating special case that is hard to carry around internally. If you want to manipulate them, you have to create special kinds of structures not provided in the initial language semantics. I am worried about two things: * Programs that read these things. In the Programmer's Apprentice project, we have programs that want to read and analyze and recode functions. If the user has a program (defun f (x) ( #+Maclisp + #-Maclisp - x 3)) and the Apprentice reads his program on the LispM where it runs, it will read (defun f (x) (- x 3)) and later when it recodes it, it will have lost the #-Maclisp information. This is bad. I don't want to have to write my own reader to handle this. * Further, if I want to construct a program piece-wise in the apprentice and then print it out, I may want to site-conditionalize things. I don't want to have to have a special printer that prints out certain forms as #+x or #-x ... I want a form that is a structured part of the language and which the Lisp printer prints normally. #+ and #- are not unique in causing this problem. #. and #, cause it, too. However, their functionality is considerably harder to express with normal syntax. #+ and #- have reasonably easy-to-contrive variants which work via normal s-expressions... at least for most cases. I won't even say #+ and #- should go away. I will, however, urge strongly that no design decisions be made which would cause the user to want to use #+ or #- to get a piece of functionality which can easily be gotten another cleaner way. The other point that I would like to make about #+ and #- while I am bad-mouthing them is that they are not structured. I have written code that does things like #+SFA X #-SFA #+BIGNUM #+MULTICS Y #-MULTICS Z #-BIGNUM #+TOPS-20 W #-TOPS-20 (ERROR "# conditional failed") and it really scares me that there's nothing to tell me that I'm not getting 2 forms in some environments or 0 in others where usually I want exactly 1. How many times have you wanted #+ELSE ? Gee. I really wish there were the equivalent s-expression conditional so I could write (FEATURE-CASEQ (SFA X) ((AND BIGNUM MULTICS) Y) (BIGNUM Z) (TOPS-20 W) (T (ERROR ...))) or some such thing just to know I can see the structure of the expression I've written. ... plus of course I'd have something my programs could read/print. By the way, while this example is a contrived case, I've really seen code much like it from time to time in the Macsyma sources. It's a real eyesore to read. Well, I guess that establishes my opinion on the situtation. I guess I think #+ and #- had their place in history but the time for using them ought to pass in Common Lisp since we can make real primitives with a clear portable meaning to replace them.  Date: Wednesday, 15 September 1982 23:19-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: OPTIMIZE Declaration It is true that #+ would do the job here, and that by using #+ we could eliminate COMPILER-VARIABLE and just hide these things under OPTIMIZE. (Some compiler variables have nothing to do with optimization, but that's a minor point.) I really hate the way #+ looks, and would like to avoid its use in Common Lisp wherever possible, even if that means introducing a new construct or two. However, I recognize that this is an irrational hangup of mine, probably the result of all the over-conditionalized and under-documented Maclisp code I have had to wade through in the last few years. If most of the group wants to go with #+ for this use, I have no real objection, though the separate COMPILER-VARIABLE form looks a lot better to me.  Date: 15 September 1982 22:48-EDT From: Earl A. Killian Subject: OPTIMIZE Declaration To: MOON at SCRC-TENEX cc: common-lisp at SU-AI The advantage of not relying on #+ is that it forces people to write portable declarations. Whether this is a real advantage is another matter.  Date: Wednesday, 15 September 1982 22:01-EDT From: MOON at SCRC-TENEX to: common-lisp at SU-AI Subject: OPTIMIZE Declaration In-reply-to: The message of 15 Sep 1982 20:51-EDT from Scott E. Fahlman Scott's suggestion for small numeric weights for the optimize dimensions sounds good. Shouldn't the compiler-variable declaration be conditionalized with #+ rather than by putting an implementation name into the form? And if that is done, shouldn't it just be additional implementation-dependent optimize dimensions, rather than an entirely separate declaration?  Date: Wednesday, 15 September 1982 22:01-EDT From: MOON at SCRC-TENEX to: common-lisp at SU-AI Subject: OPTIMIZE Declaration In-reply-to: The message of 15 Sep 1982 20:51-EDT from Scott E. Fahlman Scott's suggestion for small numeric weights for the optimize dimensions sounds good. Shouldn't the compiler-variable declaration be conditionalized with #+ rather than by putting an implementation name into the form? And if that is done, shouldn't it just be additional implementation-dependent optimize dimensions, rather than an entirely separate declaration?  Date: Wednesday, 15 September 1982 20:51-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Cc: fahlman at CMU-20C Subject: OPTIMIZE Declaration At the meeting I volunteered to produce a new proposal for the OPTIMIZE declaration. Actually, I sent out such a proposal a couple of weeks ago, but somehow it got lost before reaching SU-AI -- both that machine and CMUC have been pretty flaky lately. I did not realize that the rest of you had not seen this proposal until a couple of days ago. Naturally, this is the one thing I did not keep a copy of, so here is my reconstruction. I should say that this proposal is pretty ugly, but it is the best that I've been able to come up with. If anyone out there can do better, feel free. Guy originally proposed a format like (DECLARE (OPTIMIZE q1 q2 q3)), where each of the q's is a quality from the set {SIZE, SPEED, SAFETY}. (He later suggested to me that COMPILATION-SPEED would be a useful fourth quality.) The ordering of the qualities tells the system which to optimize for. The obvious problem is that you sometimes want to go for, say, SPEED above all else, but usually you want some level of compromise. There is no way in this scheme to specify how strongly the system should favor one quality over another. We don't need a lot of gradations for most compilers, but the simple ordering is not expressive enough. One possibility is to simply reserve the OPTIMIZE declaration for the various implementations, but not to specify what is done with it. Then the implementor could specify in the red pages whatever declaration scheme his compiler wants to follow. Unfortunately, this means that such declarations would be of no use when the code is ported to another Common Lisp, and users would have no portable way to flag that some function is an inner loop and should be super-fast, or whatever. The proposal below tries to provide a crude but adequate optimization declaration for portable code, while still making it possible for users to fine-tune the compiler's actions for particular implementations. What I propose is (DECLARE (OPTIMIZE (qual1 value1) (qual2 value2) ...), where the qualities are the four mentioned above and each is paired with a value from 0 to 3 inclusive. The ordering of the clauses doesn't matter, and any quality not specified gets a default value of 1. The intent is that {1, 1, 1, 1} would be the compiler's normal default -- whatever set of compromises the implementor believes is appropriate for his user community. A setting of 0 for some value is an indication that the associated quality is unimportant in this context and may be discrimintaed against freely. A setting of 2 indicates that the quality should be favored more than normal, and a setting of 3 means to go all out to favor that quality. Only one quality should be raised above 1 at any one time. The above specification scheme is crude, but sufficiently expressive for most needs in portable code. A compiler implementor will have specific decisions to make -- whether to suppress inline expansions, whether to type-check the arguments to CAR and CDR, whether to check for overflow on arithmetic declared to be FIXNUM, whether to run the peephole optimizer, etc. -- and it is up to him to decide how to tie these decisions to the above values so as to match the users expressed wishes. These decision criteria should be spelled out in that implementation's red pages. For example, it might be the case that the peephole optimizer is not run if COMPILER-SPEED > 1, that type checking for the argument to CAR and CDR is suppressed if SPEED > SAFETY+1, etc. A compiler may optionally provide for finer control in an implementation-dependent way by allowing the user to set certain compiler variables or switches via declarations. The policies specified by these variables would override any policies derived from the optimization values described above. The syntax would be as follows: (DECLARE (COMPILER-VARIABLE implementation (var1 val1) (var2 val2) ...)) Each implementation would choose a distinct name, and the compiler would ignore any COMPILER-VARIABLE declarations with a different implementation name. The red pages for an implementation would specify what compiler variables are available and what they do. Thus we might have (defun foo (x) (declare (compiler-variable vax (type-check-car/cdr nil)) (compiler-variable s3600 (type-check-everything t) (compiler-variable s1 (slow-down-so-the-rest-can-catch-up t))) ...) -- Scott  Date: 15 September 1982 16:44-EDT From: Jeffrey P. Golden To: Masinter at PARC-MAXC cc: common-lisp at SU-AI Date: 13-Sep-82 11:21:15 PDT (Monday) From: Masinter at PARC-MAXC Subject: Re: Case If the majority of programmers prefer lower case to upper as a way of reading their programs, programs should defaultly print out in lower case. Given a case-insensitive status-quo, should the reader coerce to lower case rather than upper? I agree with KMP's and GLS's responses to this. I don't know what the majority of programmers prefer, but I've found many programmers who prefer every which way: some lower case, some upper case, some like to capitalize the first letter of names, some like to capitalize LISP System names and quoted items but not variables. I personally find talking about LISP easiest when code is capitalized while descriptions are in mixed case so it is easy to separate the two by eye.  Date: 15 Sep 1982 1108-MDT From: Martin.Griss Subject: Re: Case To: Masinter at PARC-MAXC, common-lisp at SU-AI cc: Griss at UTAH-20 In-Reply-To: Your message of 13-Sep-82 1121-MDT I currently am used to upper case coercing in PSL etc, could get used to lower (were lower=upper in some environments...). -------  Date: 15 September 1982 1112-EDT (Wednesday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: Case usage in CL manual I guess I didn't think too hard about the choice of case in the manual; it was inherited from the LISP Machine manual and the old MacLISP manual. Perhaps MOON can lend insight here. I happen to favor case insensitivity with internal upper-case canonicalization because I find it very convenient to let case distinguish input from output (what I type is lower case, what is printed is in upper case). I admit that others might find this annoying. Except for this mild preference, I suppose I could live happily with a case-sensitive LISP provided that no one took advantage of it. (Using "car" and "Car" as distinct variable names strikes me as being on a par with using FOO and F00 as distinct variable names -- it just isn't good practice. That is my taste.) In any event, suggestions for improvement of the presentation in the CL manual are always appreciated.  Date: 14 Sep 1982 18:35 PDT From: JonL at PARC-MAXC Subject: Desensitizing case-sensitivity To: Common-Lisp@su-ai As SEF says, it looks like the issue is *nearly* unanimous now, so there's not much need for more discussion. Unfortunately, due to some kind of mailer lossage, my note on the subject, dated Sep 3, didn't get delivered; I'm reproducing it below, primarily for the benefit of comments which may tend to make the "*nearly* unanimous" choice more palatable. [p.s. these points won't be covered in the final exam for the CommonLisp reading course, but you may get extra credit for perusing them] Date: 3 Sep 1982 16:00 PDT From: JonL at PARC-MAXC Subject: Case sensitivity of CommonLisp -- Second thoughts on the modest proposal To: Kim.Jkf@Berkeley cc: CommonLisp@su-ai,franz-friends@Berkeley In-Reply-to: Jkf's msg of 29-Aug-82 22:02:05-PDT [and subsequent msgs of others] This issue is dragging on entirely too long, so I promise this to be my last entry into mire. It seems that two independent issues are brought up here, and confusion between the two has led to more flaming than necessary: 1) What is the default action of the reader -- InterLisp style (case sensitive) PDP10 MacLisp style (uppercasify non-escapeds), or Unix style (lowercasify non-escapeds). 2) What shall be the name of the standard "white pages" functions. All upper case or all lower case. Certainly I hope no one is still trying to throw out the reader escape conventions, by which *any* default choice can be ignored (i.e., backslash and vertical bar). I'm a little appalled that so few have seen the advantage of a case-sensitive reader with a shift-lockable keyboard. Having adjusted to InterLisp on the XEROX keyboard I can honestly say that I prefer it slightly to the case- insensitive MIT world that I came from. In fact, oodles of InterLisp users seem to have no trouble typing the uppercase names of standard functions (and thereby being coaxed into using mostly uppercase for their own symbols) in this case-sensitive system. Some keyboards have a shift-lock key that is less usable than desirable; even if we should adopt a case-sensitive reader (I think unlikely?) would it be in any way desireable to decide such an important issue on the basis of some keyboard manufacturer's goof? Thus I'd prefer to bypass Masinter's "modest" proposal, agreeing with Moon that it is a "radical" proposal, not because of wanting the default reader to be case-sensitive (note however, that Moon strongly objects to this) but because of the gross switch of *historic* function names from "CAR" to "car" and so on. This, I'm sure, almost anyone in the non-Franz MacLisp/Lispm community would find totally unacceptable. I refer again to the mistake made in Multics MacLisp, which adopted this notion, and the years of pain we had accommodating to it (see also Moon's commentary on this point.) In fact, Larry's later message of 31-Aug-82 18:51:03 PDT (Tuesday) makes it abundantly clear that the current (non-radical) mode of operation is a winner. As he says: I have on more than one occasion taken someone else's Interlisp program and (without very much pain) converted all of the MixedCaseIdentifiers to ALLUPPERCASE before including it in the Interlisp system (in which, although mixed case is allowed, all standard functions are uppercase to avoid confusion.) This has been acceptable. That is: "it tells the case-sensitive folks that it is OK for them to use mixed-case with sensitivity, but that if they do so, their package will have to be converted before it will be accepted into CommonLisp." Wouldn't there be less confusion if we adopted this as a modest proposal, namely that "...all standard functions are uppercase to avoid confusion." Incidentally, I view the style of the CL manual as more GLS's personal preference about readability of manuals, rather than any inherent property from which we can deduce an answer to the question in front of us. I myself would prefer UPPERCASE for white-pages function names for exactly the same reason, readability -- but this is an extremely small point and I'll be happy with whatever GLS does about it.  Date: 14 Sep 1982 18:23 PDT From: JonL at PARC-MAXC Subject: Re: `Vectors versus Arrays', and the original compromise In-reply-to: RPG's message of 13 Sep 1982 1133-PDT To: Dick Gabriel , Moon@mit-mc cc: common-lisp at SU-AI During the Nov 1981 CommonLisp meeting, the LispM folks (Symbolics, and RG, and RMS) were adamantly against having any datatype for "chunked" data other than arrays. I thought, however, that some sort of compromise was reached shortly afterwards, at least with the Symbolics folks, whereby VECTORs and STRINGs would exist in CL pretty much the way they do in other lisps not specifically intended for special purpose computers (e.g., StandardLisp, PSL, Lisp/370, VAX/NIL etc). It was admitted that the Lispm crowd could emulate these datatypes by some trivial variations on their existing array mechanisms -- all that would be forced on the Lispm crowd is some kind of type-integrity for vectors and strings, and all that would be forced on the implementors of the other CLs would be the minimal amount for these two "primitive" datatypes. Portable code ought to use CHAR or equivalent rather than AREF on strings, but that wouldn't be required, since all the generic operations would still work for vectors and strings. So the questions to be asked are: 1) How well have Lisps without fancy array facilities served their user community? How well have they served the implementors of that lisp? Franz and PDP10 MacLisp have only primitive array facilities, and most of the other mentioned lisps have nothing other than vectors and strings (and possibly bit vectors). 2) How much is the cost of requiring full-generality arrays to be part of the white pages? For example, can it be assured that all memory management for them will be written in portable CL, and thus shared by all implementations? How many different compilers will have to solve the "optimization" questions before the implementation dependent upon that compiler will run in real time? 3) Could CL thrive with all the fancy stuff of arrays (leaders, fill pointers, and even multiple-dimensioning) in the yellow pages? Could a CL system be reasonably built up from only the VECTOR- and STRING- specific operations (along with a primitive object-oriented thing, which for lack of a better name I'll call EXTENDs, as in the NIL design)? As one data point, I'll mention that VAX/NIL was so built, and clever things like Flavors were indeed built over the primitives provided. I'd think that the carefully considered opinions of those doing implementations on "stock" hardware should prevail, since the extra work engendered for the special-purpose hardware folks has got to be truly trivial. It turns out that I've moved from the "stock" camp into the "special-purpose" camp, and thus in one sense favor the current LispM approach to index- accessible data (one big uniform data frob, the ARRAY). But this may turn out to be relatively unimportant -- in talking with several sophisticated Interlisp users, it seems that the more important issues for them are the ability to have arrays with user-tailorable accessing methods (I may have to remind you all that Interlisp doesn't even have multi-dimension arrays!), and the ability to extend certain generic operators, like PLUS, to arrays (again, the reminder that Interlisp currently has no standard for object-oriented programming, or for procedural attachment).  Date: Tuesday, 14 September 1982, 01:25-EDT From: David A. Moon Subject: Reply to Gabriel on `Vectors versus Arrays' To: common-lisp at SU-AI In-reply-to: The message of 13 Sep 82 14:33-EDT from Dick Gabriel I guess this is purely a cultural difference, since my argument -against- having vectors is really exactly the same as your argument -for- having vectors: the alternative being argued against is too much cognitive overhead. I don't know why this was never brought out in the open originally.  Date: 13 Sep 1982 20:09 PDT From: JonL at PARC-MAXC Subject: Re: Clarification of full funarging and spaghetti stacks In-reply-to: dlw at SCRC-TENEX's message of Tuesday, 7 September 1982, 16:57-EDT To: Daniel L. Weinreb cc: MOON at SCRC-TENEX at MIT-MC, common-lisp at SU-AI Apologies again for being a week behind in mail (I'm trying hard to catch up!). This issue of "funarging and spaghetti stacks" came up in the context of some *private* mail I had sent to Moon, hoping to get his view of the matter first (unfortunately, the mailer here loused up the "at SCRC-TENEX" part, and he didn't get the mail). Date: 3 Sep 1982 17:59 PDT From: JonL at PARC-MAXC Subject: Re: a protest In-reply-to: MOON's message of Tuesday, 31 August 1982 17:38-EDT To: MOON at SCRC-TENEX cc: JonL,Guy.Steele@CMUA,Hedrick@Rutgers . . . GLS has already replied to this one, with cc to CommonLisp, and I'm still not sure if there is a consensus (or even a clear understanding of terms). Part of the confusion may be due to the new terminology in the CL manual (admittedly, good terminology, but still new to lispers). Anyway, it *appears* that the meaning of "funargs" in this context implies potential stack-frame retention; I think it was concern over this point ("full funargs" with indefinite scope) that brought up the discussion over a point which may be called "PROG label retention" (the indefinite extension of a dynamic "GO", namely THROW). I like the idea of CLOSUREs having indefinite extent, and also being able to "capture" selected special variables, as in the current LISPM. I don't like the idea of all environment being "closed over", which implies spaghetti.  Date: 13 September 1982 19:31-EDT From: Kent M. Pitman To: masinter at PARC-MAXC cc: Common-Lisp at SU-AI It makes a difference whether coercion is to upper or lower case. Programs have to know which to expect. Changing the direction will break programs. eg, all FASL files would have to be recompiled because READ would not get called to hack the case of the symbols dumped out in them, etc. I think the right answer is that the bulk of the existing systems already coerce to upper case and have programs relying on that fact. That being the case, I think it contrary to the principles of Common Lisp to ask that this be changed. It is an arbitrary decision and will not affect program transportability, expressive power, or whatever. The switch proposed to allow downcasing on output should satisfy the needs of those who like lowercase output, but its essential to the correctness of such a switch that all implementations coerce in the same direction and i think that uppercase is the right direction to minimize the greatest amount of hassle. If this were a new Lisp being designed from scratch, one might argue legitimately that lowercase should be the case of choice internally. I certainly wouldn't, but people might.... but since it's a language spec designed to make existing code more runnable, not less so, I think maintaining the status quo as much as possible on such issues is the right thing.  Date: 13-Sep-82 11:21:15 PDT (Monday) From: Masinter at PARC-MAXC Subject: Re: Case In-reply-to: Fahlman's message of Sunday, 12 September 1982 17:33-EDT To: common-lisp at SU-AI For the record, I don't think I voted; merely entered in a proposal. I was trying to propose a compromise to the conflicting goals of "programs should print out like they read in" and "we like to enter programs in lower case". If the majority of programmers prefer lower case to upper as a way of reading their programs, programs should defaultly print out in lower case. Given a case-insensitive status-quo, should the reader coerce to lower case rather than upper? Larry  Date: 13-Sep-82 13:20:25-PDT (Mon) From: UCBKIM.jkf@Berkeley Subject: Re: Re: Case Message-Id: <8208132020.20156@UCBKIM.BERKELEY.ARPA> Received: by UCBKIM.BERKELEY.ARPA (3.193 [9/6/82]) id a20156; 13-Sep-82 13:20:36-PDT (Mon) Received: from UCBKIM.BERKELEY.ARPA by UCBVAX.BERKELEY.ARPA (3.198 [9/12/82]) id A04850; 13-Sep-82 13:21:12-PDT (Mon) To: Guy.Steele@CMU-10A Cc: common-lisp@su-ai.fateman In-Reply-To: Your message of 13 September 1982 1558-EDT (Monday) I think that most people know that if they use a feature added at their site, then there is a good chance that their code will not be portable. I doubt that the results of asking your question would be much different than the results that I got, and even if they were the same I don't think that it would make a bit of difference to the way the Common Lisp implementors feel on this issue. I think that we will just have to wait a few years before we can judge the wisdom of making Common Lisp case-insensitive and ignoring the case-sensitive crowd.  Date: 13 Sep 1982 11:22:46-PDT From: Kim.fateman@Berkeley To: common-lisp@SU-AI Subject: vectors, arrays, etc I believe that for many future applications, the most important type of vector or array is one that corresponds to the data format of the Fortran or other numerical compiler system on the same computer. If, for example, VAX common lisp does not have this, I believe some potential users will be unhappy. Whether this should be done as a primary data type or as an optimization, is probably irrelevant if it works.  Date: 13 Sep 1982 1133-PDT From: Dick Gabriel Subject: Reply to Moon on `Vectors versus Arrays' To: common-lisp at SU-AI The difference to a user between a vector and an array is that an array is a general object, with many features, and a vector is a commonly used object with few features: in the array-is-king scheme one achieves a vector via specialization. An analogy can be made between arrays/vectors and Swiss Army knives. A Swiss army knife is a fine piece of engineering; and, having been at MIT for a while 10 years ago, I know that they are well-loved there. However, though a keen chef might own a Swiss Army knife, he uses his boning knife to de-bone - he could use his Swiss Army knife via specialization. We all think of programs as programs, not as categories with flow-of-control as mappings, and, though the latter is correct, it is the cognitive overhead of it that makes us favor the former over the latter. To me the extra few lines of code in the compiler are meaningless (why should a few extra lines bother the co-author of a 300-page compiler?); a few extra lines of emitted code are not very relevant either if it comes to that (it is , after all, an S-1). Had I been concerned with saving `a few lines of code in the compiler' you can trust that I would have spoken up earlier about many other things. The only point I am arguing is that the cognitive overhead of making vectors a degenerate array *may* be too high. -rpg-  Date: 13 Sep 1982 0016-PDT From: Dick Gabriel Subject: Mail duplications To: common-lisp at SU-AI Contrary to what I assume most of you believe, the duplication of messages is *not* due to my stupidity: it is a bug in the MAILER here combined with flakiness of SAIL wrt the ARPANET at present. When these failures occur (and they sometimes occur 10 times an hour), if a COMMON-LISP message is in progress, it is re-started - from the first person on the list. If it is any consolation, I am first on the list, so I get more duplicated messages than anyone. Also, contrary to what many of you must believe, I am not sitting here in California chuckling away at the fact you see these messages over and over: I am working with the SAIL wizards on some sort of fix, which apparently is less trivial than we thought. -rpg-  Date: Sunday, 12 September 1982 23:47-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: RPG on Vectors versus Arrays I'm sure each of us could design a better language than Common Lisp is turning out to be, and that each of those languages would be different. My taste is close to RPG's, I think: in general, I like primitives that I can build with better than generalizations that I can specialize. However, Common Lisp is politics, not art. If we can come up with a single language that we can all live with and use for real work, then we will have accomplished a lot more than if we had individually gone off an implemented N perfect Lisp systems. When my grandchildren, if any, ask me why certain things turned out in somewhat ugly ways, I will tell them that it is for the same reason that slaves count as 3/5 of a person in the U.S. Constitution -- that is the price you pay for keeping the South on board (or the North, depending). A few such crocks are nothing to be ashamed of, as long as the language is still something we all want to use. Even with the recent spate of ugly compromises, I think we're doing pretty well overall. For the record, I too believe that Common Lisp would be a clearer and more intuitive language if it provided a simple vector data type, documented as such, and presented hairy multi-D arrays with fill pointers and displacement as a kind of structure built out of these vectors. This is what we did in Spice Lisp, not to fit any particular instruction set, but because it seemed obviously right, clear, and easily maintainable. I have always felt, and still feel, that the Lisp Machine folks took a wrong turn very early when they decided to provide a hairy array datatype as primary with simple vectors as a degenerate case. Well, we proposed that Common Lisp should uniformly do this our way, with vectors as primary, and Symbolics refused to go along with this. I don't think this was an unreasonable refusal -- it would have required an immense effort for them to convert, and most of them are now used to their scheme and like it. They have a big user community already, unlike the rest of us. So we have spent the last N months trying to come up with a compromise whereby they could do things their way, we could do things our way, and everything would still be portable and non-confusing. Unfortunately, these attempts to have it both ways led to all sorts of confusing situations, and many of us gradually came to the conclusion that, if we couldn't have things entirely our way, then doing things pretty much the Lisp Machine way (with the addition of the simple-vector hack) was the next best choice. In my opinion, the current proposal is slightly worse than making vectors primary, but not much worse, and it is certainly something that I can live with. The result in this case is close to what Symbolics wanted all along, but I don't think this is the result of any unreasonable political tactics on their part. Of course, if RPG is seriously unhappy with the current proposal, we will have to try again. There is always the possibility that the set of solutions acceptable to RPG or to the S1 group does not intersect with the set acceptable to Symbolics, and that a rift is inevitable, but let us hope that it does not come down to that. -- Scott  Date: 13 September 1982 0015-EDT (Monday) From: Guy.Steele at CMU-10A To: UCBKIM.jkf at UCB-C70 Subject: Re: Case CC: common-lisp at SU-AI In-Reply-To: <8208122221.478@UCBKIM.BERKELEY.ARPA> I don't want to deprive you of the last word, and you'll still get it if you reply to this. I an curious as to what the outcome would be of a poll that includes this variant of your question 2: If a case-insensitive Common Lisp were the only lisp available on your machine, would you: a) use it, possibly with some grumbling, as a case-insensitive language? b) ask the person in charge of Common Lisp at your site to add a switch to disable the code that maps all characters to the same case, thus making it possible for each user to make Common Lisp case-sensitive, realizing that to take advantage of this switch would render your code non-portable (that is, potentially unusable at any non-Unix site, and even potentially unusable at any site but your own)? Would you be willing to take a poll on this question? (I don't insist on it, particularly if you are certain that everyone polled before realized the implication that I have spelled out explicitly in response b) above.) --Guy  Date: 12 September 1982 2344-EDT (Sunday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: Job change for Quux I have accepted a position at Tartan Laboratories, Incorporated ("Bill Wulf's company" -- notice the quote marks) beginning 1 January 1983. For this purpose I have applied to CMU for a one-year leave of absence. (The length of this leave is standard; it should not be construed as positive evidence that I will definitely return to CMU after one year, nor should this disclaimer be construed as negative evidence.) More disclaimer: I can not speak for Tartan Laboratories in any official manner at this time. Nevertheless, I think it is safe to say that in the near term I will probably be working on PQCC-type software for the construction of compilers for algebraic languages. Those of you who know me or saw the paper at the June SIGPLAN Compiler Construction conference know that I have great interest in "mainstream" compiler technology, motivated in part by a desire to apply such technology to AI languages such as LISP; the S-1 compiler leans heavily on what was learned from the BLISS-11 compiler. I hope this new job will not take me out of the LISP community. I'll be on the ARPANET, and I'll be involved in IJCAI-83 and the next LISP conference, whenever it is (in two or three years). Also, I have informal assurance from Wulf that there will be no problem with my spending a few hours a week working on Common LISP, so if everyone concerned is agreeable I will continue to edit the manual, poll for opinions, collate issues, and so on (I predict fairly rapid convergence now anyway, with most of the problems resolved by January). I cannot say whether Tartan will be interested in producing LISP compilers [disclaim, disclaim]. I think it's fair to say that they are much more likely to do so with me than without me (or someone like me, i.e., a LISP person).  Date: 12 September 1982 2323-EDT (Sunday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: ??? (that is, LET and LET*) Indeed, we voted in November not to require LET* to be a macro for precisely the reason DLW states: the "obvious" expansion runs afoul of declarations, not only SPECIALs but also type declarations. --Guy  Date: Sunday, 12 September 1982 21:23-EDT From: MOON at SCRC-TENEX To: Dick Gabriel Cc: common-lisp at SU-AI Subject: Vectors versus Arrays I think the point here, which perhaps you don't agree with, is that "vector" is not a useful concept to a user (why is a vector different from a 1-dimensional array?) It's only a useful concept to the implementor, who thinks "vector = load the Lisp pointer into a base register and index off of it", but "array = go call an interpretive subroutine to chase indirect pointers", or the code-bummer, who thinks "vector = fast", "array = slow". Removing the vector/array distinction from the guts of the language is in much the same spirit as making the default arithmetic operators generic across all types of numbers. I don't think anyone from "the Symbolics crowd convinced us that changing things were too hard for them"; our point was always that we thought it was silly to put into a language designed in 1980 a feature that was only there to save a few lines of code in the compiler for the VAX (and the S1), when the language already requires declarations to achieve efficiency on those machines. If you have a reasonable rebuttal to this argument, I at least will listen. It is important not to return to "four implementations going in four different directions."  Date: 12 Sep 1982 1623-PDT From: Dick Gabriel Subject: Vectors versus Arrays To: common-lisp at SU-AI Watching the progress of the Common Lisp committee on the issue of vectors over the past year I have come to the conclusion that things are on the verge of being out of control. There isn't an outstanding issue with regard to vectors versus arrays that disturbs me especially as much as the trend of things - and almost to the extent that I would consider removing S-1 Lisp from Common Lisp. When we first started out there were vectors and arrays; strings and bit vectors were vectors, and we had the situation where a useful data structure - derivable from others, though it is - had a distinct name and a set of facts about them that a novice user could understand without too much trouble. At last November's meeting the Symbolics crowd convinced us that changing things were too hard for them, so strings became 1-dimensional arrays. Now, after the most recent meeting, vectors have been canned and we are left with `quick arrays' or `simple arrays' or something (I guess they are 1-dimensional arrays, are named `simple arrays', and are called `vectors'?). Of course it is trivial to understand that `vectors' are a specialization of n-dimensional arrays, but the other day McCarthy said something that made me wonder about the idea of generalizing too far along these lines. He said that mathematicians proceed by inventing a perfectly simple, understandable object and then writing it up. Invariably someone comes along a year later and says `you weren't thinking straight; your idea is just a special case of x.' Things go on like this until we have things like category theory that no one can really understand, but which have the effect of being the most general generalization of everything. There are two questions: one regarding where the generalization about vectors and arrays should be, and one regarding how things have gone politically. Perhaps in terms of pure programming language theory there is nothing wrong with making vectors a special case of arrays, even to the extent of making vector operations macros on array operations. However, imagine explaining to a beginner, or a clear thinker, or your grandchildren, that to get a `vector' you really make a `simple array' with all sorts of bizarre options that simply inform the system that you want a streamlined data structure. Imagine what you say when they ask you why you didn't just include vectors to begin with. Well, you can then go on to explain the joys of generalizations, how n-dimensional arrays are `the right thing,' and then imagine how you answer the question: `why, then, is the minimum maximum for n, 63?' I guess that's 9 times easier to answer than if the minimum maximum were 7. Clearly one can make this generalization and people can live with it. We could make the generalization that LIST can take some other options, perhaps stating that we want a CDR-coded list, and it can define some accessor functions, and some auxilliary storage, and make arrays a specialization of CONS cells, but that would be silly (wouldn't it??). The point is that vectors are a useful enough concept to not need to suffer being a specialization of something else. The political point I will not make, but will leave to your imagination. -rpg-  Date: 12-Sep-82 15:21:24-PDT (Sun) From: UCBKIM.jkf@Berkeley Subject: Re: Case Message-Id: <8208122221.478@UCBKIM.BERKELEY.ARPA> Received: by UCBKIM.BERKELEY.ARPA (3.193 [9/6/82]) id a00478; 12-Sep-82 15:21:26-PDT (Sun) Received: from UCBKIM.BERKELEY.ARPA by UCBVAX.BERKELEY.ARPA (3.197 [9/11/82]) id A19168; 12-Sep-82 15:22:35-PDT (Sun) To: Fahlman@Cmu-20c Cc: common-lisp@su-ai In-Reply-To: Your message of Sunday, 12 September 1982 17:33-EDT Since I brought this whole thing up, perhaps you will permit me the last word. I think that the outcome of the vote among the implementors is clear, they like the environment they work in and they feel that everyone should 'enjoy' it. I anticipated the result of this vote in my poll and the feelings of the Unix 'users' is clear: ------- 2) If a case-insensitive Common Lisp was the only lisp available on your machine would you: a) use it without complaint about the case-insensitivity b) ask the person in charge of Common Lisp at your site to add a switch to disable the code that maps all characters to the same case, thus making it possible for each user to make Common Lisp case-sensitive. a: 3,8,9,11,12,13,16,25,26,30,34,46 b: 1,4,6,7,10,14,18,19,21,24,27,31,33,35,36,37,38,40,41,42,43,45,47,48 summary: a: 12/36 = 33% b: 24/36 = 67% ------- Should Vax common lisp ever reach the Unix world, it is clear that people will immediately add case-sensitivity as an option. There will then be programs that work in Unix Common Lisp (perhaps called Truly Common Lisp), but not in Common Lisp simply because of the case problems. Maybe then would be a good time to bring this issue up again.  Date: Sunday, 12 September 1982 17:33-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Case With the exception of the Berkeley folks and Masinter, the vote has so far been unanimous for the case-insensitive status quo. In particular, the implementors of the following systems are on record for this option, most with strong opinions: Symbolics, Spice Lisp, Vax Common Lisp, S1 Lisp, Dec-20 Common Lisp, T Lisp, and PSL. As far as I am concerned, the issue is now closed.  Date: Sunday, 12 September 1982 16:14-EDT From: MOON at SCRC-TENEX To: common-lisp at sail Subject: ENDP optional 2nd arg In our debugger, where the arguments to functions are always available, and the arguments to the function that err'ed are displayed as part of the initial error message, the extra argument to ENDP would be superfluous. I think this is a better approach since it handles the problem generally rather than handling one specific case that someone happened to think of first.  Date: Sunday, 12 September 1982 16:14-EDT From: MOON at SCRC-TENEX To: common-lisp at sail Subject: ENDP optional 2nd arg In our debugger, where the arguments to functions are always available, and the arguments to the function that err'ed are displayed as part of the initial error message, the extra argument to ENDP would be superfluous. I think this is a better approach since it handles the problem generally rather than handling one specific case that someone happened to think of first.  Date: Sunday, 12 September 1982 15:50-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: ENDP and LET* I am mildly opposed to Guy's ENDP suggestion on the grounds that it is one more damned little thing to worry about, and for some users this could be the hair that breaks the camels brain, or whatever. Having a dotted list choke EVAL is a very low probability error, unless the user is doing something where he deserves to lose, so I'd rather not make ENDP harder to use just to deal with this rare case. I am very strongly opposed to the proposal to add ¶llel and &sequential to variable-binding lists. In the rare case where the user wants ultimate control over the order in which inits are done, let him do it with SETQs and PSETQs or nested LET and LET*. -- Scott  Date: Sunday, 12 September 1982 15:50-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: ENDP and LET* I am mildly opposed to Guy's ENDP suggestion on the grounds that it is one more damned little thing to worry about, and for some users this could be the hair that breaks the camels brain, or whatever. Having a dotted list choke EVAL is a very low probability error, unless the user is doing something where he deserves to lose, so I'd rather not make ENDP harder to use just to deal with this rare case. I am very strongly opposed to the proposal to add ¶llel and &sequential to variable-binding lists. In the rare case where the user wants ultimate control over the order in which inits are done, let him do it with SETQs and PSETQs or nested LET and LET*. -- Scott  Date: Sunday, 12 September 1982 08:40-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Guy.Steele at CMU-10A Cc: common-lisp at SU-AI Subject: ??? I'd like to share a fact that I discovered when working on my compiler: LET* is not really semantically equivalent to nested LETs, even though you might think it is (and even though it used to be implemented as such a macro on the Lisp Machine!). The reason is that non-pervasive SPECIAL declarations would have their meanings altered by such a transformation. All implementors should understand this problem to avoid what might otherwise be a tempting but incorrect implementation. -------  Date: 11 September 1982 2317-EDT (Saturday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: ??? - - - - Begin forwarded message - - - - Mail-From: ARPANET host CMU-20C received by CMU-10A at 11-Sep-82 23:02:40-EDT Mail-from: ARPANET site SU-SCORE rcvd at 11-Sep-82 1558-EDT Date: 11 Sep 1982 1222-PDT From: Andy Freeman Subject: let & let* To: steele at CMU-20C I've been looking at the let/let* semantics. The way that I understand it, let* is essentially a "recursive" let. They could be defined by (although this ignores the extended syntax for the elements of the argument list of lets, does no error checking, and doesn't handle declarations) (defmacro let (args &body body) `((lambda ,(mapcar (function (lambda (arg) (cond ((atom arg) arg) (t (car arg))))) args) ,@ body) ,(mapcar (function (lambda (arg) (cond ((atom arg) nil) (t (cadr arg))))) args))) (defmacro let* (args &body body) (cond (args `(let (,(car args)) ,@ (cond ((cdr args) `((let* ,(cdr args) ,@ body))) (t body)))) (t `(progn ,@ body)))). The problem with this is that it is too sequential. The reason for using let* is that you want to put all of the variables in the same place, but to write a form where there are both parallel and sequential bindings, you have to revert to a nested form. The only way that I can think of to handle this is to extend the semantics of let by using markers, either separator tokens in the binding object list or by a third element in a binding object that should be bound after the previous element. The separator token definitions of let/let* are on the next page. ! All bindings after a token (&sequential or ¶llel) are nested. The difference between the two is that each binding after &sequential is nested while ¶llel does them in parallel. (Another possibility is to make ¶llel a nop if the bindings are already being done in parallel so that ¶llel only makes sense after a &sequential.) I like the token syntax better. It also can be used for do, prog, and ALL lambda lists. (In the latter, it only makes sense for the &aux and &optional args, and then only for the default values.) (defmacro let (args &body body) (cond ((null args) `(progn ,@ body)) ((eq (car args) '&sequential) (cond ((memq (cadr args) '(&sequential ¶llel)) (comment ignore &sequential if followed by keyword) `(let ,(cdr args) ,@ body)) (t (comment do one binding then nest) (setq args (cond ((atom (cadr args)) (cons (list (cadr args) nil) (cddr args))) (t (cdr args)))) `((lambda (,(caar args)) ,@ (cond ((cdr args) `((let ,(cons '&sequential (cdr args)) ,@ body))) (t body))) ,(cadar args))))) ((eq (car args) '¶llel) (comment ¶llel just gets ignored) '(let , (cdr args) ,@ body)) (t (do ((arg-list (mapcar (function (lambda (arg) (cond ((memq arg '(&sequential ¶llel)) arg) ((atom arg) (list arg nil)) (t arg)))) args) (cdr arg-list)) (syms nil (cons (caar arg-list) syms)) (vals nil (cons (cadar arg-list) vals))) ((or (null arg-list) (memq (car arg-list) '(&sequential ¶llel))) `((lambda ,(nreverse syms) ,@ (cond (arg-list `((let ,arg-list ,@ body))) (t body))) ,@ (nreverse vals))))))) (defmacro let* (args &body body) `(let , (cons '&sequential args) ,@ body)). -andy ------- - - - - End forwarded message - - - -  Date: 11 September 1982 2317-EDT (Saturday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: ??? - - - - Begin forwarded message - - - - Mail-From: ARPANET host CMU-20C received by CMU-10A at 11-Sep-82 23:02:40-EDT Mail-from: ARPANET site SU-SCORE rcvd at 11-Sep-82 1558-EDT Date: 11 Sep 1982 1222-PDT From: Andy Freeman Subject: let & let* To: steele at CMU-20C I've been looking at the let/let* semantics. The way that I understand it, let* is essentially a "recursive" let. They could be defined by (although this ignores the extended syntax for the elements of the argument list of lets, does no error checking, and doesn't handle declarations) (defmacro let (args &body body) `((lambda ,(mapcar (function (lambda (arg) (cond ((atom arg) arg) (t (car arg))))) args) ,@ body) ,(mapcar (function (lambda (arg) (cond ((atom arg) nil) (t (cadr arg))))) args))) (defmacro let* (args &body body) (cond (args `(let (,(car args)) ,@ (cond ((cdr args) `((let* ,(cdr args) ,@ body))) (t body)))) (t `(progn ,@ body)))). The problem with this is that it is too sequential. The reason for using let* is that you want to put all of the variables in the same place, but to write a form where there are both parallel and sequential bindings, you have to revert to a nested form. The only way that I can think of to handle this is to extend the semantics of let by using markers, either separator tokens in the binding object list or by a third element in a binding object that should be bound after the previous element. The separator token definitions of let/let* are on the next page. ! All bindings after a token (&sequential or ¶llel) are nested. The difference between the two is that each binding after &sequential is nested while ¶llel does them in parallel. (Another possibility is to make ¶llel a nop if the bindings are already being done in parallel so that ¶llel only makes sense after a &sequential.) I like the token syntax better. It also can be used for do, prog, and ALL lambda lists. (In the latter, it only makes sense for the &aux and &optional args, and then only for the default values.) (defmacro let (args &body body) (cond ((null args) `(progn ,@ body)) ((eq (car args) '&sequential) (cond ((memq (cadr args) '(&sequential ¶llel)) (comment ignore &sequential if followed by keyword) `(let ,(cdr args) ,@ body)) (t (comment do one binding then nest) (setq args (cond ((atom (cadr args)) (cons (list (cadr args) nil) (cddr args))) (t (cdr args)))) `((lambda (,(caar args)) ,@ (cond ((cdr args) `((let ,(cons '&sequential (cdr args)) ,@ body))) (t body))) ,(cadar args))))) ((eq (car args) '¶llel) (comment ¶llel just gets ignored) '(let , (cdr args) ,@ body)) (t (do ((arg-list (mapcar (function (lambda (arg) (cond ((memq arg '(&sequential ¶llel)) arg) ((atom arg) (list arg nil)) (t arg)))) args) (cdr arg-list)) (syms nil (cons (caar arg-list) syms)) (vals nil (cons (cadar arg-list) vals))) ((or (null arg-list) (memq (car arg-list) '(&sequential ¶llel))) `((lambda ,(nreverse syms) ,@ (cond (arg-list `((let ,arg-list ,@ body))) (t body))) ,@ (nreverse vals))))))) (defmacro let* (args &body body) `(let , (cons '&sequential args) ,@ body)). -andy ------- - - - - End forwarded message - - - -  Date: 11 September 1982 2326-EDT (Saturday) From: Guy.Steele at CMU-10A To: common-lisp at SU-AI Subject: KMP's remarks about ENDP KMP's remarks are well taken, and his version is certainly more concise. I do not in fact have a good feel for when to do this in general, but in trying to write EVAL I found myself using ENDP a lot, and felt that it would be a lot easier to locate the bug if some context were provided; providing this context happened always to be easy to do. But I would not be unhappy to omit this proposed "feature". --Guy  Date: 11 Sep 1982 1648-EDT From: STEELE at CMU-20C Subject: Proposal for ENDP To: common-lisp at SU-AI Recall that ENDP is the newly-recommended predicate for testing for the end of a list. I propose the small change that ENDP take an optional second argument, which is the list whose end you are checking for. All this does is allow better error reporting: (defun endp (thing &optional (list () listp)) (cond ((consp thing) nil) ((null thing) t) (listp (cerror :improperly-terminated-list "The non-null atom ~S terminated the list ~S" thing list) t) (t (cerror :improperly-terminated-list "The non-null atom ~S terminated a list" thing)))) -------  Date: 11 September 1982 18:28-EDT From: Kent M. Pitman Subject: ENDP To: Steele at CMU-20C cc: COMMON-LISP at SU-AI Couldn't you just have written this? (defun endp (thing &optional list) (cond ((consp thing) nil) ((null thing) t) (t (cerror ':improperly-terminated-list "The non-null atom ~S terminated a list~@[, ~S]." thing list)))) In any case, I definitely do not like to see functions haired up with all kinds of funny args that ideosyncratic things. There are zillions of functions which have a potential for erring and if they all take args of fun things to make the error message more readable, the language definition will be considerably more cluttered. I would want to understand some theory of when it was appropriate to add such args to things and when it wasn't before I thought it was a good idea to put this one in.  Date: 11 September 1982 18:28-EDT From: Kent M. Pitman Subject: ENDP To: Steele at CMU-20C cc: COMMON-LISP at SU-AI Couldn't you just have written this? (defun endp (thing &optional list) (cond ((consp thing) nil) ((null thing) t) (t (cerror ':improperly-terminated-list "The non-null atom ~S terminated a list~@[, ~S]." thing list)))) In any case, I definitely do not like to see functions haired up with all kinds of funny args that ideosyncratic things. There are zillions of functions which have a potential for erring and if they all take args of fun things to make the error message more readable, the language definition will be considerably more cluttered. I would want to understand some theory of when it was appropriate to add such args to things and when it wasn't before I thought it was a good idea to put this one in.  Date: 11 September 1982 18:01-EDT From: Glenn S. Burke Subject: Vote To: common-lisp at SU-AI I go for option 1. As an aside (and not to be construed as an argument for this on my part) i note that at least one place in the manual describes canonicalization of something (other than READ, i forget what it was) as being done by STRING-UPCASE. Maybe it was the names after #\. I'd have to go searching to see.  Date: 11 Sep 1982 1648-EDT From: STEELE at CMU-20C Subject: Proposal for ENDP To: common-lisp at SU-AI Recall that ENDP is the newly-recommended predicate for testing for the end of a list. I propose the small change that ENDP take an optional second argument, which is the list whose end you are checking for. All this does is allow better error reporting: (defun endp (thing &optional (list () listp)) (cond ((consp thing) nil) ((null thing) t) (listp (cerror :improperly-terminated-list "The non-null atom ~S terminated the list ~S" thing list) t) (t (cerror :improperly-terminated-list "The non-null atom ~S terminated a list" thing)))) -------  Date: Saturday, 11 September 1982 07:42-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Printing Arrays I like your proposal a lot. You seem to have cleaned up a lot of the confusion that we left in the air after the meeting. :ELEMENT-TYPE is definitely the right thing, too. My original reason for wanting printable arrays (I was only calling them multi-D vectors to be humorous, of course) was to address the general complaints I have often heard that arrays are second-class citizens in Lisp because you can't play with them as easily as you can play with lists, since they don't print. The idea was to allow APL-like interaction with Lisp, in accordance with GLS's general principle that Lisp try to adopt the good ideas and the functionality of APL. This is a pretty vague goal, and as such does not really help to resolve the issue. However, we should keep in mind that the general principle that any Lisp object should be printable in a readable way is violated in many cases throughout the language; we don't really hold this to be a general principle. It is important that objects used to represent PROGRAMS read in correctly, but anything else is just icing on the cake. So I don't think the the readability of printed arrays is really a big semantic issue. In fact, since the main thing you're worried about is whether the printed representation has to reflect the element-type, I should point out that the element-type is only an efficiency issue (except for strings etc, but they already print differently) and so it is not semantically necessary (mostly) to worry about their preservation; it's mainly an efficiency issue. And if you are worried about efficiency, maybe then it is reasonable to say that you should use some better representation for your arrays than text that needs parsing. (This is a somewhat bogus argument since efficiency in saving and loading is not the same as efficiency in computation, but I think the spirit is right.) -------  Date: Saturday, 11 September 1982 07:44-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Array proposal (long msg) Moon asks how sequence-returning functions decide whether to turn on the print bit. Actually, how to they decide whether to put in a leader and other random attributes like that? The same problem came up with DEFSTRUCT long ago, and the random :MAKE-ARRAY option was put in to fix it, but I'd hate to see a :MAKE-ARRAY parameter added to every sequence function unless it is necessary. -------  Date: Saturday, 11 September 1982 07:33-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: Kent M. Pitman Cc: COMMON-LISP at SU-AI Subject: Array proposal Making all vectors (1-D arrays) default to printing is wrong. What's so special about 1-dimensionality. Arrays created by typing in the #(...) syntax would have their printing-bit set, of course.... I thought the idea was that vectors should be simple and effectively "option-free". No, that was last week's jargon. In the new jargon, "vector" means a 1-D array, whereas the simple thing you are talking about is now called a SIMPLE array. So, what you are really saying is that the print-bit should be another one of those things that SIMPLE arrays cannot hack. SEF's proposal, on the other hand, pretty clearly states that :PRINT is orthogonal to "all of the above" attributes, but I don't know whether he really intended to say that or not. Being of the Lisp Machine persuasion, I don't care a lot about exactly which restrictions should be imposed on SIMPLE arrays and which should not; I'm not qualified to have an opinion. People who care about this should discuss it. -------  Date: Saturday, 11 September 1982 07:21-EDT Sender: DLW at MIT-OZ From: DLW at MIT-MC To: common-lisp at su-ai Subject:Vote Speaking for the Symbolics Common Lisp effort, and on behalf of Dave Moon and Howard Cannon, I vote for option 1. We feel rather strongly about this and, like SEF, will only budge if there is very strong opposition to this vote. -------  Date: Saturday, 11 September 1982, 01:22-EDT From: Robert W. Kerns Subject: Re: SETF and friends [and the "right" name problem] To: JonL at PARC-MAXC Cc: common-lisp at SU-AI In-reply-to: The message of 2 Sep 82 13:33-EDT from JonL at PARC-MAXC Date: 2 Sep 1982 10:33 PDT From: JonL at PARC-MAXC Apologies for replying so late to this one -- have been travelling for a week after AAAI, and *moving to a new house* -- but I want to add support to your comments. Me too. Moving, that is. I just got to your message, and only because it had me as a recipient directly instead of on a mailing-list. I now live in Brighton. (What a pain! About two weeks shot to hell, between looking and moving...and still no phone because of the *&^@#^% phone company screwing up my order, as usual.) So how's the house?  Date: 9 Sep 1982 1709-MDT From: Martin.Griss Subject: Case To: common-lisp at SU-AI cc: griss at UTAH-20 I vote for Case-insensitive, as in PSL. We coerce to upper (unless a switch is flipped). -------  Date: Thursday, 9 September 1982 14:42-EDT From: Scott E. Fahlman To: common-lisp at SU-AI Subject: Printing Arrays At the 8/21 meeting someone (Weinreb, maybe?) was arguing that we ought to have "Mulit-D vectors", by which he meant arrays that would print out in a simple readable format. The :PRINT proposal is an attempt to deal with this issue, but since I don't understand quite what uses were envisioned for these things, I can't decide whether it is OK for these things just to print in a format that displays their elements and dimensions, but not details like whether the element-type is restricted. If the desire is just to have an array that people can examine, and that reads back in to something EQUALP to the original, that is easy to do; if the applications require that the printed object reads back in and turns into the exact same type-restricted form that we started with, things get ugly. I think the idea was just to have a class of arrays that were easy to look at, and I will proceed on that assumption in revising the array proposal -- if I'm wrong about this, somebody had better speak up pretty soon. -- Scott  Date: 9 September 1982 05:29-EDT From: Jeffrey P. Golden Subject: Vote on Cases To: Common-Lisp at SU-AI I vote for option 1. (I am just a user and peruser of the Common Lisp mail.)  Date: 9 September 1982 03:11-EDT From: Kent M. Pitman Subject: Array proposal To: COMMON-LISP at SU-AI Date: Wednesday, 8 September 1982 18:25-EDT From: MOON at SCRC-TENEX ... The CHAR and BIT functions can go away since they are just duplications of AREF. Programs for some implementations might want to define macros that generate AREF with a THE declaration... I disagree with this. While it may be the case that you will want to make CHAR and BIT trivially turn into just AREFs, I think they have value in terms of self-documentation. Particularly, I would rather see: (DEFUN FIRSTCHAR (STRING) (CHAR STRING 1)) than (DEFUN FIRSTCHAR (STRING) (AREF STRING 1)) even if the two were identically efficient. Also, automatic translators to dialects or languages not part of Common Lisp will have a considerably easier time if people use programs that make a visual distinction between characters and arrays even if there is not one so that useful optimizations may be done where appropriate. I imagine this would help out the T group considerably when it comes time to write a Common Lisp compatibility package. Further, I don't even know what you mean by "Programs for some implementations...". I thought the whole idea behind Common Lisp was that code should port well from implementation to implementation. If people on the LispM write code using AREF because they know it'll be fast there, then they're throwing away useful information that would allow their code to run faster in some other implementation. If you don't expect people to write code for the LispM which is to be ported to machines that'll need declarations, then I think you're drifting from the goals fo a common lisp. Making all vectors (1-D arrays) default to printing is wrong. What's so special about 1-dimensionality. Arrays created by typing in the #(...) syntax would have their printing-bit set, of course.... I thought the idea was that vectors should be simple and effectively "option-free". They should not waste a lot of space storing information like how they print. They're mostly a hack to allow implementors to write lots of fancy optimizations. If you start hairing them up with things like print options, pretty soon you'll be back up to the level of arrays. I support the idea that they should all follow some set of fixed print conventions.  Date: 9 September 1982 02:31-EDT From: Kent M. Pitman Subject: PRINT/READ inversion To: COMMON-LISP at SU-AI Besides printing/reading code (and how many people use #.(MAKE-ARRAY ...) to make random constants in their code? hopefully not too many), what other applications were there for printing/reading strings? Many things (the LispM patch file directories come to mind as a simple example) save strings as a way of saving objects whose only property is printed representation. Such things are safe to print with "...". I bet people don't do much saving of strings that have hairy parts ... eg, who would ever want to save out ZWEI line objects to a file and read them back in? I'm curious if anyone has ever had need in some real program for writing out strings which had hairy attributes and reading them back in... Without real cases to ponder over, it's hard to be sure I'm thinking about the right issues. Also, it occurred to me that the syntax "..." might be a good printed syntax for `simple' strings and that #"..." might be a good syntax for strings whose printed representation didn't show the whole story and therefore should be read errors on input (eg, like ZWEI's line objects). This would leave people with the problem of coming up with another syntax for bit strings so maybe it's a bad idea. -kmp ps for those not familiar with ZWEI, the LispM's editor, it stores editor buffers essentially by doubly-linked chains of strings. The array leader of each line in the buffer contains a slot for a pointer to the line object which is the previous line and another for the following line, so that by doing clever references to array leaders you can essentially cdr your way forward or backward through the buffer. The problems involved in writing a printer -- even one using #.(...) -- which could print out these objects in a truly READ-invertable form would be tremendous because of the odd kinds of circularities present; the structure is obviously quite circular.  Date: Wednesday, 8 September 1982 18:25-EDT From: MOON at SCRC-TENEX To: Scott E. Fahlman Cc: common-lisp at SU-AI Subject: Array proposal This is good. the type of the elements, as specified by the :TYPE keyword to MAKE-ARRAY (actually, I would much prefer :ELEMENT-TYPE as the keyword for this option, since :TYPE is confusing here). I am strongly in favor of this. The current :TYPE keyword to MAKE-ARRAY means something entirely different from element-type, but I had given up hope of getting it back after it was "stolen". :ELEMENT-TYPE is much clearer. The CHAR and BIT functions can go away since they are just duplications of AREF. Programs for some implementations might want to define macros that generate AREF with a THE declaration. Making all vectors (1-D arrays) default to printing is wrong. What's so special about 1-dimensionality. Arrays created by typing in the #(...) syntax would have their printing-bit set, of course. How do sequence-returning functions decide what to use for the printing-bit of their result? There is a fairly serious conflict between wanting strings with fill-pointers to print as ordinary strings, and wanting them to print in a way that reads as a string with a fill-pointer. I don't have a suggestion about this, especially since I am not a strong believer in printing things out and reading them back in anyway.  Date: 8 September 1982 15:27-EDT (Wednesday) From: FEINBERG at CMU-20C To: Scott E. Fahlman Cc: Common-Lisp at SU-AI Subject: Vote on Cases Howdy! Speaking as a Lisp user, I vote for option 1, status quo.  Date: 08 Sep 1982 1018-PDT From: Dick Gabriel Subject: Case vote To: common-lisp at SU-AI As another 1/3 of the S-1 Lisp implementors (and the head of the project), I vote for option 1. -rpg-  Date: Wednesday, 8 September 1982 13:06-EDT From: Jonathan Rees To: Fahlman at CMU-20C Cc: Common-Lisp at SU-AI Subject: Vote on Cases I don't know whether I rate voting status or not, but in case I do: Speaking for the Yale's T implementation project (T is a portable Scheme-like Lisp dialect) and for Yale's Lisp users (which includes Maclisp, UCI Lisp, Franz Lisp, and T users), I strongly urge Common Lisp to retain its current case-insensitive status quo, that is, option 1. of Fahlman's recent message. Since the reasons for this position have been discussed at length I will make no mention of them even though I (we) feel strongly. T might become the base for yet another Common Lisp implementation sometime next year (we're still mulling this one over), but if it does, Common Lisp's decision on case won't matter much, since Common Lisp will be implemented as an incompatible "compatibility mode" in any case [sic]. However, compatibility with T on this issue will make life a whole lot easier for us should we decide to go ahead with the project.