Getting Imperative

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.

The IMPERATIVE Macro

The 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.

The macro

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.

Deodorizing?

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.