;;; Examples of destructive actions + some macros. $ gcl GCL (GNU Common Lisp) 2.6.12 ANSI Nov 27 2014 05:23:43 Source License: LGPL(gcl,gmp), GPL(unexec,bfd,xgcl) Binary License: GPL due to GPL'ed components: (READLINE UNEXEC) Modifications of this banner must retain notice of a compatible license Dedicated to the memory of W. Schelter Use (help) to get some basic information on how to use GCL. Temporary directory for compiler files set to /private/var/folders/1h/3prjrhy96y55j8jrvttgd_kw0000gp/T/ >(setq 'a '(1 2 4 5)) Error: Fast links are on: do (si::use-fast-links nil) for debugging Signalled by SETQ. Condition in SETQ [or a callee]: INTERNAL-SIMPLE-TYPE-ERROR: (QUOTE A) is not of type SYMBOL: Broken at SETQ. Type :H for Help. 1 Return to top level. >>1 ;; Oops. SETQ expects a symbol, not a quoted symbol; it's a special form (not evaluating its ;; first argument) exactly to save us the typing of that '. I could just remove the ', ;; but I'll use SET, which does evaluate it. Top level. >(set 'a '(1 2 4 5)) (1 2 4 5) >(setq b '(6 7 8 55)) (6 7 8 55) >(help 'nconc) ----------------------------------------------------------------------------- NCONC [Function] (not found "gcl.info") From ((NCONC . Lists) gcl-si.info): -- Function: NCONC (&rest lists) Package:LISP Concatenates LISTs by destructively modifying them. ----------------------------------------------------------------------------- >(help 'append) ----------------------------------------------------------------------------- APPEND [Function] (not found "gcl.info") From ((APPEND . Lists) gcl-si.info): -- Function: APPEND (&rest lists) Package:LISP Constructs a new list by concatenating its arguments. ----------------------------------------------------------------------------- >(nconc a b) (1 2 4 5 6 7 8 55) >a (1 2 4 5 6 7 8 55) >b (6 7 8 55) >(RPLAC b 1000) ; oops, typo again it's RPLACA I meant. Error: Fast links are on: do (si::use-fast-links nil) for debugging Signalled by EVAL. Condition in EVAL [or a callee]: INTERNAL-SIMPLE-UNDEFINED-FUNCTION: Cell error on RPLAC: Undefined function: Broken at EVAL. Type :H for Help. 1 Return to top level. >>1 Top level. >(RPLAC b 1000) RPLACA RPLACD >(RPLACA b 1000) (1000 7 8 55) >b (1000 7 8 55) ;; Oh look, we changed A as well! Of course, NCONC just changed the last CDR of A to point to the first CONS cell of B. >a (1 2 4 5 1000 7 8 55) ;; What will nconc do when its first argument is NIL? Let's catch the result in a variable. >(setq x (nconc nil '(a b c))) ; oops, not really what I wanted. I wanted to save a pointer to the second argument, too. (A B C) >(setq y '(a b c)) ; ok, saving it now (A B C) >(setq x (nconc nil 'y)) ; oops, not what I wanted either, typed an extra '. Y >(setq x (nconc nil y)) ; now got it (A B C) >(eq x y) ; So X and Y point to the same thing (the first cons cell of Y) T ;; Now let's see what happens when NCONC gets a _symbol_ whose value is NIL. >(setq a nil) NIL >(setq b '(10 20 290)) (10 20 290) >(nconc a b) (10 20 290) >b (10 20 290) ;; A remains NIL. Stands to reason: its value NIL is no cons cell, and doesn't have a CDR for NCONC to replace (and no CAR either). >a NIL ;; What if we wanted a MYNCONC that would set its first argument to NCONC's return value if it's a symbol, even ;; nil-valued? This is not a good idea as most destructive ops, but what if we still want it? ;; Clearly, no function can behave that way, because a function only gets the results of evaluating ;; its arguments, not the arguments themselves. So it won't get the symbol, but just a nil (its value). ;; ;; We need a special form that acts similar to SETQ. This can be done with a macro. ;;; First, let's just get the SETQ part. >(DEFMACRO mynconc (x y) (list 'setq 'x y)) ; not quite right---see how it expands below MYNCONC >(MACROEXPAND '(mynconc a b)) (SETQ X B) ; this is wrong: we want A, not X as a 1st arg T >(DEFMACRO mynconc (x y) (list 'setq x y)) MYNCONC >(MACROEXPAND '(mynconc a b)) (SETQ A B) T >(mynconc s '(1 2 3)) (1 2 3) >s (1 2 3) ;; OK, let's add NCONC >(DEFMACRO mynconc (x y) (list 'setq x (nconc x y))) ;; not quite right either MYNCONC >(MACROEXPAND '(mynconc a b)) Error: Fast links are on: do (si::use-fast-links nil) for debugging Signalled by NCONC. Condition in NCONC [or a callee]: INTERNAL-SIMPLE-TYPE-ERROR: A is not of type LIST: Broken at NCONC. Type :H for Help. 1 Return to top level. >>1 Top level. ;; So, the body of DEFMACRO is evaluated to build the list we want the macro to expand to. >(DEFMACRO mynconc (x y) (list 'setq x '(nconc x y))) ; not quite right either! MYNCONC >(MACROEXPAND '(mynconc a b)) (SETQ A (NCONC X Y)) ; we don't want literal symbols for X and Y, but whatever we pass as args, like A and B T ;; So, we need an extra constructed list that evaluates its args, not just a quoted list that doesn't: >(DEFMACRO mynconc (x y) (list 'setq x (list 'nconc x y))) ;; now it's correct. MYNCONC >(MACROEXPAND '(mynconc a b)) (SETQ A (NCONC A B)) T ;; The macroexpansion is finally what we wanted. Testing it. >(setq l1 nil) NIL >(setq l2 '(xx yy zz)) (XX YY ZZ) >(mynconc l1 l2) (XX YY ZZ) >l1 (XX YY ZZ) >l2 (XX YY ZZ) >(eq l1 l2) T >(setq l1 '(aa bb cc)) (AA BB CC) >(mynconc l1 l2) (AA BB CC XX YY ZZ) >l1 (AA BB CC XX YY ZZ) >l2 (XX YY ZZ) >(setq l1 '(aa bb cc)) (AA BB CC) >(setq l2 '(xx yy zz)) (XX YY ZZ) >(setq l1 nil) NIL >(mynconc l1 l2) (XX YY ZZ) >l1 (XX YY ZZ) >(RPLACA l1 1001) (1001 YY ZZ) >l1 (1001 YY ZZ) >l2 (1001 YY ZZ) ;; Writing a call to LIST every time we want parens in the expanded macro body is tedious. ;; There's a backtick-shorthand ` for "listify everything but the things with a ,": >(DEFMACRO mynconc1 (x y) `(setq ,x (nconc ,x ,y))) MYNCONC1 >(MACROEXPAND (mynconc1 bob alice)) ; Oops, forgot to quote the expression, got it evaluated instead of passing to MACROEXPAND as is Error: Fast links are on: do (si::use-fast-links nil) for debugging Signalled by SETQ. Condition in SETQ [or a callee]: INTERNAL-SIMPLE-UNBOUND-VARIABLE: Cell error on BOB: Unbound variable: Broken at NCONC. Type :H for Help. 1 Return to top level. >>1 Top level. ;; Both macros expand exactly the same way; ` is just shorthand. >(MACROEXPAND '(mynconc1 bob alice)) ; now correct (SETQ BOB (NCONC BOB ALICE)) T >(MACROEXPAND '(mynconc bob alice)) (SETQ BOB (NCONC BOB ALICE)) T ;; Recursively comparing the lists structurally with EQUAL: >(equal (MACROEXPAND '(mynconc bob alice)) (MACROEXPAND '(mynconc1 bob alice))) T >l1 (1001 YY ZZ) ;; And now for some fun. What happens if we destructively NCONC a list with itself? ;; That would be replacing the CDR of the last cell with the pointer to the list's first one. >(nconc l1 l1) (1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 YY ZZ 1001 ;;; and MUCH more of this, until I pressed Ctrl+C. I deleted 35 Megabytes of output here! Correctable error: Fast links are on: do (si::use-fast-links nil) for debugging Signalled by SYSTEM::GCL-TOP-LEVEL. If continued: Type :r to resume execution, or :q to quit to top level. SIMPLE-ERROR: Console interrupt. Broken at SYSTEM::GCL-TOP-LEVEL. Type :H for Help. 1 (continue) Type :r to resume execution, or :q to quit to top level. 2 Return to top level. >> 2 Top level. ;; Again :) This will happen every time. The Print part of the REPL ;; starts printing out the return value, and loops forever. >(setq l1 '(aa bb cc)) (AA BB CC) >(RPLACA l1 1001) (1001 BB CC) >l1 (1001 BB CC) >(NCONC l1 l1) (1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 BB CC 1001 ;;; and so on till Ctrl+C. The rest deleted. Correctable error: Fast links are on: do (si::use-fast-links nil) for debugging Signalled by SYSTEM::GCL-TOP-LEVEL. If continued: Type :r to resume execution, or :q to quit to top level. SIMPLE-ERROR: Console interrupt. Broken at SYSTEM::GCL-TOP-LEVEL. Type :H for Help. 1 (continue) Type :r to resume execution, or :q to quit to top level. 2 Return to top level. >>2 Top level. ;; This is one reason why destructive operations should be used very sparingly. ;; There are many others.