Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

May 2004
Sun Mon Tue Wed Thu Fri Sat
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Apr  Jun

Nesting backquotes

Sunday, May 23, 2004

Psst - want to see some interesting examples of nesting backquotes in CL? David Steuber asked for some on c.l.l. and got three different interesting examples for three different application requirements:

  1. Defining business rules
  2. Generating HTML
  3. Converting Lisp to Java
Here they are:

#1: Coby Beck gave a short example that is in production code:

(defmacro define-business-rule
    ((name &optional short long) &key field-type formula entities doc)
  `(progn
     ,@(mapcar #'(lambda (context)
		   `(set-formula ',(intern (string-upcase name))
				 ',context ',formula))
	       entities)
     ,@(when formula
	     (mapcar #'(lambda (ent)
			 `(merge-fields
			   ,(keywordify ent)
			   ',(list (make-field-syntax
				    `((,name ,short ,long)
				      :type ,(keywordify field-type)
				      :formula ,(expand-formula formula ent)
				      :note ,doc)
				    :derived))))
		     entities))))
It allows him to concisely specify calculated fields in a database application. For example:

(define-business-rule ("ReqProfitMargin" "Required Profit") 
  :field-type money
  :formula ($multiply TenderMarkup TenderTotalCost)
  :entities (WorkItem ProjectPart))
and get:
(progn (set-formula 'reqprofitmargin 'workitem
                    '($multiply tendermarkup tendertotalcost))
       (set-formula 'reqprofitmargin 'projectpart
		    '($multiply tendermarkup tendertotalcost))
       (merge-fields :workitem
		     '((("cReqProfitMargin" "Required Profit") (:money NIL NIL)
			($multiply (:projectpart . :tendermarkup)
			 ($if ($eql :projectpartcode "0.00") 0
			      ($add ($add :estimatedengineeringcost
					  ($add :operationalmargin
						:corporatemargin)) :onsiteoverheadcost))))))
       (merge-fields :projectpart
		     '((("cReqProfitMargin" "Required Profit") (:money NIL NIL)
			($multiply (:project . :tendermarkup) :tendertotalcost)))))
#2: Wade Humeniuk provided the following example of an HTML defining macro:

(defmacro deftag (tagsym &key element attributes (end-tag t))
  (let* ((docstring 
	  (format nil "~A: <~S>.  The <~S> Tag has the attributes ~{ ~S~}." 
		  (or element "Generic Tag")
		  tagsym tagsym attributes))
	 (tag-macrosym (intern (format nil "<~S>" tagsym)))
	 (endtag-macrosym (intern (format nil "</~S>" tagsym)))
         (keys (mapcar (lambda (attr)
                         (intern (symbol-name attr) :keyword))
                       attributes))
	 (tag-arglist (mapcar (lambda (attr) (list attr nil)) attributes)))
    `(progn
       (defmacro ,tag-macrosym (&key ,@tag-arglist (stream '*primitive-html-stream*))
         ,docstring
         (let ((non-nil-attributes
                (loop for attribute in (list ,@attributes)
		   for attrsym in '(,@keys)
		   when attribute
		   collect attrsym and
		   collect attribute)))
           `(write-tag ',',tagsym ,stream ,@non-nil-attributes)))
       (export ',tag-macrosym)
       (unless (eq ,end-tag :forbidden)
         (defmacro ,endtag-macrosym (&key (stream '*primitive-html-stream*))
           `(write-end-tag ',',tagsym ,stream))
         (export ',endtag-macrosym)))))
For example, to use this macro to create the HTML <b> tag you code the following:
(deftag b
    :element "Bold Text"
    :attributes #.*%attrs*)
#3: Antonio Menezes Leitao created this beauty for the Linj ("Lisp in Java" or "Linj is not Java") language (commentary below is his):
(defmacro def-swing-constructor (class-superclass (&rest setters) &body body)
  (multiple-value-bind (class superclass)
      (if (consp class-superclass)
	  (values-list class-superclass)
	  (values class-superclass nil))
    (let ((all-slots 
	   (append setters 
                   (gethash superclass *swing-constructors* (list)))))
      (setf (gethash class *swing-constructors*) all-slots)
      `(defmacro ,class (&key ,@(mapcar #'(lambda (setter)
					    `(,setter nil 
						      ,(conc-symbol setter
								    '-p)))
					all-slots))
	 `(let ((,',class (new ',',class)))
	    ,@,@(mapcar #'(lambda (setter)
			    `(when ,(conc-symbol setter '-p)
			       `((send ,',class
                                       ,',(conc-symbol 'set- setter)
                                       ,,setter))))
			all-slots)
	    ,@'(,@body)
	    (setf the-component ,',class))))))
To use this, you write something like:
(def-swing-constructor test ;;a class name
    (a			    ;;their 'initialization' args
     b)
  (foo)	;;further initialization
  (bar))
and it expands into:
(defmacro test (&key (a () a-p) (b () b-p))
  `(let ((test (new 'test)))
     ,@(when a-p `((send test set-a ,a)))
     ,@(when b-p `((send test set-b ,b)))
     (foo)
     (bar)
     (setf the-component test)))
The idea is that it allows you to replace the initialization of instances that require setters with initialization using initargs. For those who don't know Swing (the Java library for graphical interfaces), it's enough to say that there are so many configuration options in the library classes that they just couldn't put then as constructor parameters (because they don't have keyword parameters in Java). The result is that they use setters to define those options. Unfortunately, this makes the construction of Swing graphical interfaces a real PITA. You just can't write functional code because everything is done via setters.

Using Linj, of course, you have other solutions. The above little macro is just an experiment I'm doing that allows me to "convert" setters into initargs. I'll show just the definition for the JLabel class:
(def-swing-constructor (j-label j-component)
    (text
     icon
     horizontal-alignment
     vertical-alignment
     disabled-icon
     displayed-mnemonic
     horizontal-text-position
     vertical-text-position 
     icon-text-gap
     label-for))
(def-swing-constructor component (component-orientation cursor drop-target locale location name size)) The j-label definition then composes the initargs for its class hierarchy and expands into:
(defmacro j-label
    (&key (text () text-p) (icon () icon-p)
     (horizontal-alignment () horizontal-alignment-p)
     (vertical-alignment () vertical-alignment-p)
     (disabled-icon () disabled-icon-p)
     (displayed-mnemonic () displayed-mnemonic-p)
     (horizontal-text-position () horizontal-text-position-p)
     (vertical-text-position () vertical-text-position-p)
     (icon-text-gap () icon-text-gap-p) (label-for () label-for-p)
     (action-map () action-map-p) (alignment-x () alignment-x-p)
     (alignment-y () alignment-y-p) (autoscrolls () autoscrolls-p)
     (background () background-p) (border () border-p)
     (debug-graphics-options () debug-graphics-options-p)
     (double-buffered () double-buffered-p) (enabled () enabled-p)
     (font () font-p) (foreground () foreground-p)
     (input-verifier () input-verifier-p)
     (maximum-size () maximum-size-p) (minimum-size () minimum-size-p)
     (next-focusable-component () next-focusable-component-p)
     (opaque () opaque-p) (preferred-size () preferred-size-p)
     (request-focus-enabled () request-focus-enabled-p)
     (tool-tip-text () tool-tip-text-p) (u-i () u-i-p)
     (verify-input-when-focus-target () verify-input-when-focus-target-p)
     (visible () visible-p) (layout () layout-p) (bounds () bounds-p)
     (component-orientation () component-orientation-p)
     (cursor () cursor-p) (drop-target () drop-target-p)
     (locale () locale-p) (location () location-p) (name () name-p)
     (size () size-p))
  `(let ((j-label (new 'j-label)))
     ,@(when text-p `((send j-label set-text ,text)))
     ,@(when icon-p `((send j-label set-icon ,icon)))
     ,@(when horizontal-alignment-p
	     `((send j-label set-horizontal-alignment ,horizontal-alignment)))
     ,@(when vertical-alignment-p
	     `((send j-label set-vertical-alignment ,vertical-alignment)))
     ,@(when disabled-icon-p
	     `((send j-label set-disabled-icon ,disabled-icon)))
     ,@(when displayed-mnemonic-p
	     `((send j-label set-displayed-mnemonic ,displayed-mnemonic)))
     ,@(when horizontal-text-position-p
	     `((send j-label set-horizontal-text-position
		     ,horizontal-text-position)))
     ,@(when vertical-text-position-p
	     `((send j-label set-vertical-text-position ,vertical-text-position)))
     ,@(when icon-text-gap-p
	     `((send j-label set-icon-text-gap ,icon-text-gap)))
     ,@(when label-for-p `((send j-label set-label-for ,label-for)))
     ,@(when action-map-p `((send j-label set-action-map ,action-map)))
     ,@(when alignment-x-p `((send j-label set-alignment-x ,alignment-x)))
     ,@(when alignment-y-p `((send j-label set-alignment-y ,alignment-y)))
     ,@(when autoscrolls-p `((send j-label set-autoscrolls ,autoscrolls)))
     ,@(when background-p `((send j-label set-background ,background)))
     ,@(when border-p `((send j-label set-border ,border)))
     ,@(when debug-graphics-options-p
	     `((send j-label set-debug-graphics-options ,debug-graphics-options)))
     ,@(when double-buffered-p
	     `((send j-label set-double-buffered ,double-buffered)))
     ,@(when enabled-p `((send j-label set-enabled ,enabled)))
     ,@(when font-p `((send j-label set-font ,font)))
     ,@(when foreground-p `((send j-label set-foreground ,foreground)))
     ,@(when input-verifier-p
	     `((send j-label set-input-verifier ,input-verifier)))
     ,@(when maximum-size-p `((send j-label set-maximum-size ,maximum-size)))
     ,@(when minimum-size-p `((send j-label set-minimum-size ,minimum-size)))
     ,@(when next-focusable-component-p
	     `((send j-label set-next-focusable-component
		     ,next-focusable-component)))
     ,@(when opaque-p `((send j-label set-opaque ,opaque)))
     ,@(when preferred-size-p
	     `((send j-label set-preferred-size ,preferred-size)))
     ,@(when request-focus-enabled-p
	     `((send j-label set-request-focus-enabled ,request-focus-enabled)))
     ,@(when tool-tip-text-p
	     `((send j-label set-tool-tip-text ,tool-tip-text)))
     ,@(when u-i-p `((send j-label set-u-i ,u-i)))
     ,@(when verify-input-when-focus-target-p
	     `((send j-label set-verify-input-when-focus-target
		     ,verify-input-when-focus-target)))
     ,@(when visible-p `((send j-label set-visible ,visible)))
     ,@(when layout-p `((send j-label set-layout ,layout)))
     ,@(when bounds-p `((send j-label set-bounds ,bounds)))
     ,@(when component-orientation-p
	     `((send j-label set-component-orientation ,component-orientation)))
     ,@(when cursor-p `((send j-label set-cursor ,cursor)))
     ,@(when drop-target-p `((send j-label set-drop-target ,drop-target)))
     ,@(when locale-p `((send j-label set-locale ,locale)))
     ,@(when location-p `((send j-label set-location ,location)))
     ,@(when name-p `((send j-label set-name ,name)))
     ,@(when size-p `((send j-label set-size ,size)))
     (setf the-component j-label)))
And now, in the appropriate context, you can write:
(label :text "Hi" :name "my-label" :border 2 :opaque nil)
and you get:
(let ((label (new 'j-label)))
  (send label set-text "hi")
  (send label set-border 2)
  (send label set-opaque nil)
  (send label set-name "my-label")
  (setf the-component label))
Linj then translates this into the following Java code fragment:

JLabel label = new JLabel()
label.setText("hi")
label.setBorder(2)
label.setOpaque(false)
label.setName("my-label")
theComponent = label

Things become more interesting when you combine this with layout constructors. Here is one example:
(table ()
       (tr (td (j-label :title "Foo")) (td (j-text-field) :colspan 5))
       ...)
This hugely simplies the construction of Swing interfaces.

emacs Copyright © 2004 by Bill Clementson