Subject: Re: please review my new *working* code - now with 20% more oop!
From: rpw3@rpw3.org (Rob Warnock)
Date: Mon, 23 Feb 2009 20:11:41 -0600
Newsgroups: comp.lang.lisp
Message-ID: <2t-dnV2TcazAyj7UnZ2dnUVZ_s7inZ2d@speakeasy.net>
Leandro Rios <leandroprogramador@gmail.com> wrote:
+---------------
| Remember the earmuffs convention.
..[and a DESTRUCTURING-BIND suggestion]...
+---------------
 
To which David <notmas@gmail.com> replied:
+---------------
| (defvar *wk*)
| (defvar *wb*)
| (defvar *wn*)
| (defvar *bk*)
| (defvar *a*)
| 
| (destructuring-bind (wk-temp wb-temp wn-temp bk-temp)
|                     (get-positions)
|   (setq *wk* (rank-file wk-temp)
|         *wb* (rank-file wb-temp)
|         *wn* (rank-file wn-temp)
|         *bk* (rank-file bk-temp)))
| 
| (setf *a* (make-instance 'chessboard))
| 
| (put-piece *a* (first wk)(second wk) 'wk)
| (put-piece *a* (first wb)(second wb) 'wb)
| (put-piece *a* (first wn)(second wn) 'wn)
| (put-piece *a* (first bk)(second bk) 'bk)
+---------------

Two comments:

First, I suspect Leandro was really trying to suggst eliminating the
*WK*/*WB*/*WN*/*BK* global variables *entirely*! Something like this:

    (defvar *a* (make-instance 'chessboard))

    (destructuring-bind (wk wb wn bk)
	(mapcar #'rank-file (get-positions))
      (macrolet ((frob (x)
		   `(put-piece *a* (first ,x) (second ,x) ',x)))
	(frob wk)
	(frob wb)
	(frob wn)
	(frob bk)))

Or more straightforwardly [IMHO!]:

    (defvar *a* (make-instance 'chessboard))

    (loop for piece in '(wk wb wn bk)
	  and (rank column) in (mapcar #'rank-file (get-positions))
      do (put-piece *a* rank column piece))

Secondly [and somewhat contrary to the above], I personally think
there's nothing wrong with doing a little fast prototyping with global
variables, *if* instead of DEFVAR (with earmuffs on the variables)
one uses DEFLEX[1] (with no earmuffs) for those sorts of developmental
convenience globals. One particular advantage, which I didn't discover
until after I'd been using DEFLEX for a while, is that once you've
figured out what you're doing they're *really* easy to fold into a LET*,
without having to do any massive renaming to get rid of the earmuffs.

For example, I once wrote a quick little hack to scan some CL source
code and build a dependency tree of the DEFSTRUCT includes. The first
cut had a section that looked like this:

    (deflex filename "some/long/fixed/path/to/foo.lisp")

    (deflex node-forms (file-forms filename))

    (deflex structs (remove 'defstruct node-forms
                            :key #'car :test-not #'eq))

    (deflex name-and-options (mapcar #'second structs))

    (defun prune (name-and-options)
      "Given the 2nd subform of a DEFSTRUCT, if there was an :INCLUDE
      option return a list of the structure name and the name of the
      included parent structure, else just retutn the structure name."
      (cond
        ((symbolp name-and-options)
         name-and-options)
        ((atom name-and-options)
         (error "Bad name-and-options: ~s" name-and-options))
        ((find :include (cdr name-and-options) :key #'car)
         (list (car name-and-options)
               (cadr (find :include (cdr name-and-options) :key #'car))))
        ((symbolp (car name-and-options))
         (car name-and-options))
        (t
         (error "Bad name-and-options: ~s" name-and-options))))

    (deflex names-and-includes (mapcar #'prune name-and-options))

    ...[then a topological sort and a tree walk and I was done].

The advantage of this is, of course, that you can get each step
working correctly in isolation before moving on to the next. The
variable are sitting right there at the top-level, easy to print,
poke at, whatever, but guaranteed *not* to conflict with any bound
lexicals in any functions you might be calling, since they're
"lexicals" themselves. And once it was all working, it was then
easy to delete the first token of each of the DEFLEX forms, wrap
a LET* and DEFUN around them, and get this much cleaner form:

    (defun prune (name-and-options) ...[as above]...)

    (defun struct-names-and-includes (filename)
      (let* ((node-forms (file-forms filename))
             (structs (remove 'defstruct node-forms
                              :key #'car :test-not #'eq))
             (name-and-options (mapcar #'second structs)))
        (mapcar #'prune name-and-options)))

[Note that this style of working *doesn't* tend to leave any
DEFLEX forms in your final, production version, which is probably
a good thing until the meme is a little more widely spread...]


-Rob

[1] <http://rpw3.org/hacks/lisp/deflex.lisp>, or something equivalent.

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607