A few weeks ago I was messing around with CLOG when I noticed a peculiar pattern. In several the excellent examples that ship with CLOG, the
let* form is used to emulate an imperative style of programming. A good example of this can be found here. The faint putrescence of a code smell wafted up from that example, and, I thought, “Darn it, it should be better. It can be better!”
Isn't Common Lisp a multi-paradigm language? Why shouldn't this imperative pattern be expressed more naturally?
The result of my curiosity is a rough draft of a macro, called
imperative macro allows for the interleaving of binding forms with arbitrary non-binding forms without deep nesting. Here is how it might be used:
(imperative (:= x 10 y (+ x 20) z (+ x 100)) (print (list x y z)) (setf x 100) (print (list :x x)) (:= x 101) (print (list :x x)) (list x y z)) ;; which would print ;; (10 30 110) ;; (:X 100) ;; (:X 101) ;; then return ;; (101 30 110)
Under the hood, the macro just nests
LET* forms. For example, a
macroexpand-1 of the above yeilds:
(BLOCK NIL (LET* ((X 10) (Y (+ X 20)) (Z (+ X 100))) (PRINT (LIST X Y Z)) (SETF X 100) (PRINT (LIST :X X)) (LET* ((X 101)) (PRINT (LIST :X X)) (LIST X Y Z))))
The whole thing is inside of a
BLOCK so that you can return early if you wish by calling
(return &optional value) anywhere inside.
It's not perfect, but here's the macro in its current form:
(defmacro imperative (&body body) "Evaluate expressins in BODY in sequence. Expressions that look like (:= VAR1 EXPR1 ...) will expand into a LET* form whose bindings are valid for the rest of the BODY E.g. (imperative (format t \"Welcome to IMPERATIVE\") (terpri) (:= x 10 z (+ x 20)) (format t \"X = ~a, Z = ~a~%\" x z) (:= y (+ z 20)) (format t \"Y = ~a~%\" y) (list x y z)) would evaluate to: Welcome to IMPERATIVE ;; <-- printed to stdout X = 10, Z = 30 Y = 50 (10 50 30) ;; <-- return value IMPERATIVE introduces an implicit, anonymous BLOCK, and hence can be returned from. " (labels ((binding-form-p (form) (and (consp form) (keywordp (first form)) (eql := (first form)))) (collect-bindings (bindings) (loop for (var expr . more) on bindings by #'cddr collect (list var expr))) (expander (body) (cond ((null body) body) ((binding-form-p (first body)) (list (list* 'let* (collect-bindings (rest (first body))) (expander (rest body))))) (t (cons (first body) (expander (rest body))))))) `(block () ,@(expander body))))
Good enough as a first draft.
A partial re-write of the smelly example is here:
(imperative (:= last-tab nil t1 (create-button body :content "Tab1") t2 (create-button body :content "Tab2") t3 (create-button body :content "Tab3")) (create-br body) (:= p1 (create-div body) p2 (create-div body) p3 (create-div body :content "Panel3 - Type here") f1 (create-form p1) fe1 (create-form-element f1 :text :label (create-label f1 :content "Fill in blank:"))) (create-br f1) (:= fe2 (create-form-element f1 :color :value "#ffffff" :label (create-label f1 :content "Pick a color:"))) (create-br f1) (create-form-element f1 :submit :value "OK") (create-form-element f1 :reset :value "Start Again") ;; .. this is a long one, but you get the idea ... )
I can't tell whether or not the deodorizor smells any better.