How about
defun? I don't want to program in Common Lisp like this:
(eval-when (:load-toplevel :execute) ;; don't evaluate functions in compiler
(setf (symbol-function 'add)
(lambda (x y)
(block add (+ x y))))
when I can program like this:
(defun add (x y) (+ x y))
The nice thing is that I could remove the eval-when dance and have functions defined in the compiler by default, and the block could be omitted when I obviously don't need it. The remaining syntax is still ugly; I'd want
my-defun.
Usually people whip out the LOOP macro, so let's skip that.
How about pattern matching (TXR Lisp). A simple expression like:
(if-match (:pre :amble (@x) @(oddp @y)) obj
(list x y))
churns out quite a mouthful:
5> (macroexpand-1 '(if-match (:pre :amble (@x) @(oddp @y)) obj
(list x y)))
(alet ((#:g0051 obj))
(let* (#:result-0064
x y)
(if (if (consp #:g0051)
(alet ((#:g0052 (car #:g0051))
(#:g0053 (cdr #:g0051)))
(if (equal #:g0052 ':pre)
(progn (if (consp #:g0053)
(alet ((#:g0054 (car #:g0053))
(#:g0055 (cdr #:g0053)))
(if (equal #:g0054 ':amble)
(progn (if (consp #:g0055)
(alet ((#:g0056 (car #:g0055))
(#:g0057 (cdr #:g0055)))
(if (consp #:g0056)
(alet ((#:g0058 (car #:g0056))
(#:g0059 (cdr #:g0056)))
(progn (set x #:g0058)
(if (equal #:g0059 '())
(progn (if (consp #:g0057)
(alet ((#:g0060 (car #:g0057))
(#:g0061 (cdr #:g0057)))
(progn (set y #:g0060)
(alet ((#:res-0063
(alet ((y #:g0060))
(oddp y))))
(if #:res-0063
(if (equal #:g0061 '())
(progn (progn (set #:result-0064
(list x y))
t)))))))))))))))))))))))
#:result-0064
I wouldn't write it in that mechanical way by hand; but writing it in a nice way would take thought.
The optimizer does a good job of reducing that kind of code, and the code walks through the list only once, using temporaries for successive cdr values.
The way I might write it might look terse, but involve redundant accesses to the list from the start.
Common Lisp's simple destructuring pattern operator destructuring-bind is also a macro.
[1]> (macroexpand '(destructuring-bind (a (b c (d))) list (list a b c d)))
(LET ((SYSTEM::<DESTRUCTURING-FORM> LIST))
(IF (NOT (SYSTEM::LIST-LENGTH-IN-BOUNDS-P SYSTEM::<DESTRUCTURING-FORM> 2 2 NIL))
(SYSTEM::DESTRUCTURING-ERROR SYSTEM::<DESTRUCTURING-FORM> '(2 . 2))
(LET*
((A (CAR SYSTEM::<DESTRUCTURING-FORM>)) (#:G3318 (CADR SYSTEM::<DESTRUCTURING-FORM>))
(#:G3319
(IF (NOT (SYSTEM::LIST-LENGTH-IN-BOUNDS-P #:G3318 3 3 NIL))
(SYSTEM::ERROR-OF-TYPE 'SOURCE-PROGRAM-ERROR :FORM SYSTEM::<DESTRUCTURING-FORM> :DETAIL #:G3318
(SYSTEM::TEXT "~S: ~S does not match lambda list element ~:S") 'DESTRUCTURING-BIND #:G3318 '(B C (D)))
#:G3318))
(B (CAR #:G3319)) (C (CADR #:G3319)) (#:G3320 (CADDR #:G3319))
(#:G3321
(IF (NOT (SYSTEM::LIST-LENGTH-IN-BOUNDS-P #:G3320 1 1 NIL))
(SYSTEM::ERROR-OF-TYPE 'SOURCE-PROGRAM-ERROR :FORM #:G3319 :DETAIL #:G3320
(SYSTEM::TEXT "~S: ~S does not match lambda list element ~:S") 'DESTRUCTURING-BIND #:G3320 '(D))
#:G3320))
(D (CAR #:G3321)))
(LIST A B C D)))) ;
T
This example is from CLISP. Ut uses a lot of CADDR from the root of the list. Still a mouthful that is not nice to write by yourself, even in the nicest, tersest possible way.
Defining object, with complex bells and whistles (TXR Lisp):
2> (macroexpand-1 '(defstruct myclass (these are super classes)
(:static static-slot 42)
uninitialized-slot
(initialized-slot 73)
(:method meth (me : optional-arg) (list em optional-arg))
(:postinit (me))
(:fini (me) (put-string `finalizer invoked on @me`))))
** expr-1:1: warning: defstruct: inheritance base these does not name a struct type
** expr-1:1: warning: defstruct: inheritance base are does not name a struct type
** expr-1:1: warning: defstruct: inheritance base super does not name a struct type
** expr-1:1: warning: defstruct: inheritance base classes does not name a struct type
(sys:make-struct-type 'myclass '(these are super
classes)
'(static-slot meth)
'(uninitialized-slot
initialized-slot)
(lambda (#:g0019)
(when (static-slot-p #:g0019 'meth)
(static-slot-set #:g0019 'meth
(sys:meth-lambda
meth myclass
(me : optional-arg)
(block meth (list em optional-arg)))))
(when (static-slot-p #:g0019 'static-slot)
(static-slot-set #:g0019 'static-slot
42)))
(lambda (#:g0019)
(finalize #:g0019 (sys:meth-lambda
myclass :fini
(#:g0019) (let ((me #:g0019))
(put-string `finalizer invoked on @me`)))
t)
(let ((#:g0020 (struct-type #:g0019)))
(unless (static-slot-p #:g0020 'uninitialized-slot)
(slotset #:g0019 'uninitialized-slot
()))
(unless (static-slot-p #:g0020 'initialized-slot)
(slotset #:g0019 'initialized-slot
73))))
() ())
To write the function call, you have to know the API, which is ugly. You need to write a bunch of lambdas. You will not get a warning for the function that you're using undefined bases (and other useful diagnostics).
defstruct supports custom clauses you can define using define-struct-clause. There are some predefined ones for declaratively doing delegation between classes and what not.
The implementation of assignable places ("generalized variables") is done through macrology. All the place-mutating operators like setf, incf or push are macros. They analyze the syntax of the place and use the material from the back-end definitions to write the code, ensuring that complex places are not evaluated multiple times. Programs can define new kinds of places.
Object access? In TXR Lisp there is a macro qref. It corresponds to a dotted read notation:
1> '(qref a b c (d arg))
a.b.c.(d arg)
2> (expand 'a.b.c.(d arg))
(let ((#:g0024 (slot (slot a 'b)
'c)))
(call (slot #:g0024 'd)
#:g0024 arg))
If we suspect a.b might be null:
12> (expand 'a.b.?c.(d arg))
(let ((#:g0025 (slot a 'b)))
(if #:g0025 (let ((#:g0026 (slot #:g0025 'c)))
(call (slot #:g0026 'd)
#:g0026 arg))))
Lisps are built on macros. And macros are something users have access to also, to do the same things for themselves.
You should use the quality, documented macros that are already provided. That's why that coding conventions document says that you should use macros liberally, just define them sparingly.
Lisp being built on macros has some nice advantages like if you have to work with an older version of some Lisp which didn't have some language feature, you may be able to backport those macros to polyfill that.