Monday, September 15, 2008

I'll Take a Mulligan

When the going gets weird, the weird turn pro. - Hunter S. Thompson
Well, summer's come and gone and it was about as surreal as they get. Hopefully, the next few months will prove to be positive. I'll be working hard to finally get my company off the ground.

Will be back posting here soon. Not sure how soon - could be this week or next. But I'm probably gonna take a Mulligan, take most of the posts down here, move the coherent ones to blackgrit.com and make papers and/or downloadable code out of others. Those that I don't like will end up on the cutting room floor.

I have been coding so I'll write about what I've been working on. I'll also create download space on blackgrit.com for some of the packages that have been in the works for so long.

I'll keep you posted.

Sunday, August 10, 2008

Lisp Scales 2 - Class Hierarchy

The following 10 minute scale is a direct translation of a Java sample program that calculates the area of a couple of shapes. Source, www.java2s.com - Class Hierarchy example.

Both are similar examples of OOP in action. I made a couple of small changes to the Lisp code. The first change is to the name of the collection class. The original author called theirs "ClassShapeMain". I wasn't crazy about that name, so I called mine "shape-collection".

Another change I made was to a method name. The Java author called the method that calculates the total area of all the shapes in the collection "totalAreas". I called mine "area" and let method dispatch figure out which area to call based on instance type.

Also added three test functions. One to generate random shapes. Another to test a set of 'n' random shapes (optional argument; default 10.) And a third to assert that the areas are being calculated correctly.

One of the nice features of CLOS (Common Lisp Object System), is method combination - being able to define :before, primary and :after (and :around) methods for a given signature. See A Brief Guide to CLOS for more detail. I added a diagnostic output by declaring a :before method on area. It calls describe on the object passed. I've included the output generated from execution so you can see how the function 'describe' prints instance objects.

The output of the Lisp program is at the end of the post.

import java.util.*;

/** Part of a main program using Shape objects */
public class ClassShapeMain {

Collection allShapes; // created in a Constructor, not shown

/** Iterate over all the Shapes, getting their areas */
public double totalAreas() {
Iterator it = allShapes.iterator();
double total = 0.0;
while (it.hasNext()) {
Shape s = (Shape)it.next();
total += s.computeArea();
}
return total;
}
}
class Circle extends Shape {
double radius;
public double computeArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double width, height;
public double computeArea() {
return width * height;
}
}
abstract class Shape {
protected int x, y;
public abstract double computeArea();
}

And now the CLOS version:

(defclass shape-collection ()
((all-shapes :initform ()
:accessor all-shapes
:initarg :all-shapes)))

(defgeneric area (s))

;;; I initialize each instance of shape with x and y equal to 0. If this was
;;; common, it'd be easier to add ':initform 0' to each (x and y) slot in shape.
(defclass shape ()
((x :initarg :x
:accessor x)
(y :initarg :y
:accessor y)))

(defclass circle (shape)
((radius :initarg :radius
:accessor radius)))

(defclass rectangle (shape)
((width :initarg :width
:accessor width)
(height :initarg :height
:accessor height)))

(defmethod area :before (obj)
"Diagnostic output of obj - calls describe to print instance at repl."
(describe obj))

(defmethod area ((c circle))
"Calculate the area of a circle."
(* pi (expt (radius c) 2)))

(defmethod area ((r rectangle))
"Calculate the area of a rectangle."
(* (width r) (height r)))

(defmethod area ((sc shape-collection))
"Calculate and return the total area of the instances in shape-collection."
(let ((total 0))
(dolist (shape (all-shapes sc))
(setf total (+ total (area shape))))
total))

;;; TEST CODE

(defun random-shape ()
"Generate a random shape - either circle or rectangle instance and return it."
(if (> (mod (random 10) 2) 0)
(make-instance 'circle :radius (random 100) :x 0 :y 0)
(make-instance 'rectangle :width (random 25) :height (random 25) :x 0 :y 0)))

(defun test-random-shapes (&optional (num-shapes 10))
(area (make-instance 'shape-collection :all-shapes (loop for x from 0 to num-shapes
collecting (random-shape)))))

(defun test-assert-shapes ()
;; should succeed
(assert (= (area (make-instance 'circle :radius 1 :x 0 :y 0)) pi))
(assert (= (area (make-instance 'rectangle :width 1 :height 1 :x 0 :y 0)) 1))
(assert (= (area (make-instance 'rectangle :width 2 :height 2 :x 0 :y 0)) 4))
;; should fail
(assert (= (area (make-instance 'circle :radius 2)) pi)))

Here's the output at the Lisp repl from running 'test-shapes:

CL-USER 10 > (test-shapes)

# is a SHAPE-COLLECTION
ALL-SHAPES (# # # # # # # # # # ...)
# is a RECTANGLE
WIDTH 1
HEIGHT 13
X 0
Y 0
# is a RECTANGLE
WIDTH 24
HEIGHT 17
X 0
Y 0
# is a CIRCLE
RADIUS 64
X 0
Y 0
# is a CIRCLE
RADIUS 58
X 0
Y 0
# is a CIRCLE
RADIUS 64
X 0
Y 0
# is a RECTANGLE
WIDTH 10
HEIGHT 16
X 0
Y 0
# is a CIRCLE
RADIUS 82
X 0
Y 0
# is a CIRCLE
RADIUS 36
X 0
Y 0
# is a RECTANGLE
WIDTH 19
HEIGHT 14
X 0
Y 0
# is a RECTANGLE
WIDTH 24
HEIGHT 1
X 0
Y 0
# is a RECTANGLE
WIDTH 12
HEIGHT 7
X 0
Y 0
;;; Everything above this line is diagnostic output.

62454.81778667379D0 ;;; <<< The total area returned from the shape-collection.

And this is the output from calling 'test-assert-shapes'

CL-USER 13 > (test-assert-shapes)

# is a CIRCLE
RADIUS 1
X 0
Y 0
# is a RECTANGLE
WIDTH 1
HEIGHT 1
X 0
Y 0
# is a RECTANGLE
WIDTH 2
HEIGHT 2
X 0
Y 0
# is a CIRCLE
RADIUS 2
X 0
Y 0
Error: The assertion (= (AREA (MAKE-INSTANCE # :RADIUS 2)) PI) failed.
1 (continue) Retry assertion.
2 (abort) Return to level 0.
3 Return to top loop level 0.

Type :b for backtrace, :c

Note the assertion failed as expected. I'll show more options that 'assert' offers in another post.

Thursday, July 31, 2008

Lisp Scales #1 1/2, Small Array Sample

Still moving. Wrote this the other day, not sure why - found some better Java tutorial code to use as exercises.

public class ScoresAverage {

public static void main(String[] args)
{
double scores[] = { 76.0, 84.5, 92.5, 88.0, 96.0 };
double sum= 0;
double average;

for (int i = 0; i < scores.length; i++)
{
sum += scores[i];
}

average = sum / scores.length;

System.out.println("The average of the scores is " + average);
System.out.println(scores[4] + " is the fourth element in the scores array.");
System.out.println("There are " + scores.length + " elements in this array.");
}
}

Working with lists is more convenient than working with arrays in Lisp. But, in Lisp, things that aren't easy can usually be made to be easy pretty easily. Yes, I know, that's a lot of easy - welcome to Lisp! For this half-scale, I wrote a couple of functions to make processing one-dimensional arrays (simple-vector) a little easier.

The whole purpose of this scale is to calculate the average of a set of values in an array. Three different approaches are presented.
  1. An exact knockoff of the Java code.

  2. A lispier version that uses a couple of functions - coerce (builtin) and avg (custom)

  3. A version that uses the array without consing. Create some functions/macro that can be used as general-purpose utilities: sv+, dosvref, svactive and svavg.


Paul Graham, in OnLisp, describes when to write a function or macro. He also describes when a function might benefit from being written as a macro. In the case of avg he points out, "Consider for example the operator avg, which returns the average of its arguments. It could be defined as a function but there is a good case for defining it as a macro, because the function version would entail an unnecessary call to length each time avg was called. At compile-time we may not know the values of the arguments, but we do know how many there are, so the call to length could just as well be made then."

Link: http://www.bookshelf.jp/texi/onlisp/onlisp_9.html#SEC63

The two versions he presents are as follows. Since this code will call apply on avg, it'll use the function version (since you can't call funcall or apply on a macro):

(defun avg (&rest args)
(/ (apply #'+ args) (length args)))

(defmacro avg (&rest args)
`(/ (+ ,@args) ,(length args)))

This first Lisp version mirrors the Java code above. The #() syntax creates and populates an array.

(let* ((scores-array #(76.0 84.5 92.5 88.0 96.0))
(total-score 0))
;; Mirror the Java code and iterate to calculate the total score. Can call 'loop or 'dotimes.
(loop for x from 0 below (array-total-size scores-array)
do (setf total-score (+ total-score (svref scores-array x))))
(format t "The average of the scores is ~F~%" (/ total-score (array-total-size scores-array)))
(format t "~D is the ~:R element in the scores-array.~%" (svref scores-array 4) 4)
(format t "There are ~D elements in the scores-array.~%" (array-total-size scores-array)))

The format expression ~:R will output a number as '(first second third fourth ...). The format function in Lisp is comprehensive.

This next version converts the array to a list via the coerce call. The resulting list is passed to the function avg. Works, but it conses, which is generally a bad thing if it can be avoided.

(let* ((scores-array #(76.0 84.5 92.5 88.0 96.0)))
(format t "The average of the scores is ~F~%" (apply #'avg (coerce scores-array 'list)))
(format t "~D is the ~:R element in the scores-array.~%" (svref scores-array 4) 4)
(format t "There are ~D elements in the scores-array.~%" (array-total-size scores-array)))

A couple of array utilities. dosvref is a macro that provides iteration over a simple vector. sv+ sums the entries in a simple-vector (array). svavg is the array-equivalent function for avg above.

(defmacro dosvref ((val array) &body body)
"dolist equivalent for simple vector. Iterate over array elements up to
either fill-pointer (if there is a fill-pointer) or array-total-size."
(let ((i (gensym)))
`(dotimes (,i (if (array-has-fill-pointer-p ,array)
(fill-pointer ,array)
(array-total-size ,array)))
;; bind val for use in body
(let ((,val (aref ,array ,i)))
,@body))))

(defun sv+ (array)
"Sum the values of the elements of array."
(let ((acc 0))
(dosvref (v array)
(setf acc (+ acc v)))
acc))

(defun svactive (array)
"Assume data in array is contiguous starting at position zero.
Either return the fill-pointer or an equivalent, calculated, value for the last,
non-nil value in the array. This would be more efficient using a
divide-and-conquer approach."
(if (array-has-fill-pointer-p array)
(fill-pointer array)
(let ((cnt (array-total-size array)))
(loop for i from (1- (array-total-size array)) downto 0
when (not (svref array i))
do (decf cnt)
else return cnt))))

(defun svavg (array)
"Avoid consing up a new list (eg. by calling coerce). Return the avg."
(/ (sv+ array) (svactive array)))

This last version of the equivalent Java code uses svavg to avoid consing up a new list.

(let ((scores-array #(76.0 84.5 92.5 88.0 96.0)))
(format t "The average of the scores is ~S~%" (svavg scores-array))
(format t "~D is the ~:R element in the scores-array.~%" (svref scores-array 4) 4)
(format t "There are ~D elements in the scores-array.~%" (array-total-size scores-array)))

The output:
The average of the scores is 87.4
96.0 is the fourth element in the scores-array.
There are 5 elements in the scores-array.

Lisp Scales #1 - Basic Math

No time for anything but the equivalent of musical scales today. Converted straightforward code from the Java Basic Math Demo to Lisp.

public class BasicMathDemo {
public static void main(String[] args) {
double a = -191.635;
double b = 43.74;
int c = 16, d = 45;

System.out.printf("The absolute value of %.3f is %.3f%n", a, Math.abs(a));
System.out.printf("The ceiling of %.2f is %.0f%n", b, Math.ceil(b));
System.out.printf("The floor of %.2f is %.0f%n", b, Math.floor(b));
System.out.printf("The rint of %.2f is %.0f%n", b, Math.rint(b));
System.out.printf("The max of %d and %d is %d%n",c, d, Math.max(c, d));
System.out.printf("The min of of %d and %d is %d%n",c, d, Math.min(c, d));
}
}

Here's some equivalent Lisp code. The function 'math-fun' takes a function and a list of args and prints the name of the function, the numeric arguments and the result of applying the function to the args. The 'main-math-demo calls math-fun with the arguments as they're called in the Java code.

The construct ~{ ~F~} in the format call in math-fun is called an iteration construct that manages processing a list of arguments "as if for a recursive call to format".

(defun math-fun (function &rest args)
(format t "The ~A of~{ ~F~} is ~F.~%" function args (apply function args)))

(defun main-math-demo (a b c d)
(mapcar #'(lambda (args)
(apply #'math-fun args))
`((abs ,a)
(ceiling ,b)
(floor ,b)
(round ,c)
(max ,c ,d)
(min ,c ,d))))

CL-USER > (main-math-demo -191.635 43.74 16 45)
The ABS of -191.635 is 191.635.
The CEILING of 43.74 is 44.0.
The FLOOR of 43.74 is 43.0.
The ROUND of 16.0 is 16.0.
The MAX of 16.0 45.0 is 45.0.
The MIN of 16.0 45.0 is 16.0.
(NIL NIL NIL NIL NIL NIL)

Note that the Lisp version of max and min can take one or more numeric (non-complex) args. For example,

CL-USER > (max 1 2 3 4 5 6 7 8 9)
9
CL-USER > (max 5.0 2)
5.0
CL-USER > (max 1.0s0 7.0d0)
7.0d0

The backquote combines with comma to splice values into the list. Compare this:

(list (list 'abs a)
(list 'ceiling b)
(list 'floor b)
(list 'round c)
(list 'max c d)
(list 'min c d))
to the equivalent:

`((abs ,a)
(ceiling ,b)
(floor ,b)
(round ,c)
(max ,c ,d)
(min ,c ,d))

Saturday, July 26, 2008

Some Lisp Code: Pathnames and a Little More

I promised some code and (gulp!) here's a bit of it. Directory traversal, basic file find by type, cd and pwd. Will post expanded code (eg. ls and other mods/additions) later.

The following is a combination of code I've written, as well as publically available code - sources are listed in function/method comments and at the end of this post. No guarantees of completeness or accuracy (I'm pretty certain that it's ok to use the code, though.) It's been tested under CMUCL so far.

If you are kind enough to take the time to review the code and notice something that's not right or would benefit from optimization (or if somebody's done it better), please let me know. Nothing worse than bad code being passed off as good.

Oof, no sooner had I written that previous paragraph when...I just re-reviewed Peter Seibel's Portable Pathname chapter and realized that my code is working for CMUCL, if only by chance. Since his code's stable and more complete, I'll update this post later today by incorporating Peter's pathname code (Common Lisp Tutorial here.)

I'm pretty certain that with the file-find code (and subsequent thoughts of grep) that I've stepped towards and/or onto the turf of (at least) Montezuma. If that's the case, I'll take a close look at incorporating it into this mosh pit of happiness that I'm concocting. Which would be cool - I've been wanting to take it out for a spin for a while. If not, I'll start adding features to find, ls and other commands to expand their feature-set. Kinda looks like Hemlock's dired, too.

Where's this all heading...at this point, my emphasis is on the UI. Last week I made a small fix to an interface and it opened up possibilities that I was completely unaware of. Which kind of pissed me off because I'd ignored fixing a nagging problem for longer than I'm willing to say (because it worked "well enough".) But I was also pretty stoked because it's cool when you learn something new that offers some interesting possibilities.

Regardless, proof of concept requires I write test code until I understand what I'm doing/building. After that, it makes sense to use stable libs or other code that's available to extend the functionality. So, consider this a rough first pass. Any updates will be highighted.

Red Light! Green Light!

Our new apartment with the roof leak has come back into focus. They claim we're good to move in. Bottom line is I'll be moving and it might be even tougher to post until the move's over.

Regardless, here's some code. Let me know if it's useful or you get a laugh out of it. Especially if you get a laugh out of it...

(defgeneric directories (arg))

;;; Utilities

(defun last1 (lst)
"Source: Paul Graham, OnLisp, http://www.paulgraham.com/onlisp.html"
(car (last lst)))

(defmacro aif (test-form then-form &optional else-form)
"Anaphoric if. Evaluate test-form binding it to the result and execute the form based on the result of it.
Source: Paul Graham, OnLisp, http://www.paulgraham.com/onlisp.html"
`(let ((it ,test-form))
(if it ,then-form ,else-form)))

(defmacro awhen (expr &body body)
"Anaphoric when, a variant of anaphoric if.
Source: Paul Graham, OnLisp, http://www.paulgraham.com/onlisp.html"
`(aif ,expr
(progn ,@body)))

;;; Directory utilities

(defun directory-p (path)
"Return boolean value specifying whether the path is a directory.
Source: http://www.gigamonkeys.com/book/, A Common Lisp Tutorial"
(and path
(not (pathname-name path))
(not (pathname-type path))))

(defmethod directories ((path pathname))
"Return the directory pathnames in the directory specified in path."
(remove-if-not #'directory-p (directory path)))

(defmethod directories ((paths list))
"Filter directory pathnames from list."
(remove-if-not #'directory-p paths))

(defun short-dir-names (path)
"Return the last list entry in pathname-directory."
(mapcar #'last1 (mapcar #'pathname-directory (directories path))))

(defun ascend-dir (path &optional limit)
"Ascend path one level. If directory name is string= to limit, return the current path (at limit). If limit is nil, return path if cdr of pathname-directory is nil."
(let ((pdir (pathname-directory path)))
;; If path is root directory, return path.
(if (and (null limit)
(null (cdr pdir)))
path
;; If path is eq to limit, return path.
(if (string= (last1 pdir) limit)
path
(make-pathname :directory (butlast pdir))))))

;; Close over wd, providing access via 'pwd and 'cd.
(let ((wd (user-homedir-pathname)))
(defun pwd ()
(princ (list 'working-directory wd))
(terpri)
wd)
(defun cd ()
(awhen (traverse-dir (pwd))
(setf wd it))))

(defun filter-files (path type)
"Make a pathname using :directory of path and :type of type and filter via call to directory."
(directory (make-pathname :directory (pathname-directory path) :type type)))

(defun file-find (path &rest types)
"Find files (depth-first) of type(s) in the directory specified by path. Returns a list with each entry a list of form '(path ((type-0) (type-n)))."
(let (res)
(labels ((filter-dirs (p types)
(push (cons p (mapcar #'(lambda (type)
(filter-files p type))
types))
res)
;; Descend to the subdirectories of pathname 'p.
(dolist (pd (directories p))
(filter-dirs pd types))))
(filter-dirs path types))
(reverse res)))


Sources:
Paul Graham, On Lisp (download). http://www.paulgraham.com/onlisp.html
and an online version for functions: last1, aif, awhen
Peter Siebel's pathname extensions. http://www.gigamonkeys.com/book/practical-a-portable-pathname-library.html

Friday, July 25, 2008

Dude, Where's the Code?

George: "That's why I'm different. I can sense the slightest human suffering."
Jerry: "Are you sensing anything right now?"
- Seinfeld, "The Couch"
In my last post, the last paragraph, I said I'd be posting code shortly. Ahem...I like to think I'm ok at what I do, but I'm one guy. Any distraction stifles forward progress and yesterday a it was simply a traffic jam that did me in.

I'll be posting code soon. Possibly later. Maybe in chunks. Sheesh, I don't know how I'm ever gonna get a company off the ground like this...hopefully my software will help me save face or I'm screwed.

While reviewing my code for clarity next to the man page for ls, I took a gander to see whether there was other Lisp code out there that was more mature and stable than my own. It's Lisp, it's 50 years old. Of course there's better code out there! Much of it was probably written when I was a kid.

Yeah, I know I did the whole #'with-sinister-laugh thing, but research has to be balanced with pragmatism. Build it or buy it, right? Especially here in the Lisp world, where there's so much code that is mature and stable and well-written and documented and freely available. I know, I said that already.

Saw some libs that looked promising. I'll look at them today.

So, in the interest of quality control, I'll be posting my code after a review, probably alongside a review or notes for code or a library that supports the features of the commands I'm building.

"Looks like I picked the wrong week to stop sniffin' glue..."

Wednesday, July 23, 2008

Interactive Development in Lisp and Unix Commands in Lisp

I forgot to mention one small but important detail in the last article about incremental development using Lisp.

While building the guts of the app, I never quit it. Added a line of code, triggered a menu, tested and verified the new change. Lather, rinse, repeat. That level of interactivity certainly makes incremental development easy (passe even.)

Lisp is cool that way. The editor's (Emacs, Hemlock and others) provide interfaces to external Lisp's, so you can have your app running in a separate process, too. If your app terminates for any reason, your editor process is still running. That's pretty normal. The interactive development is paranormal.

Congratulations, it's Unix?

Wrote some directory manipulation code today. A variation on a theme that's been done, done-over and over-done. Built Unix equivalents for cd, ls, and pwd with more to come. Why? Why-oh-why indeed.

Want to make it easy to move around, see what's where and interact with contextual info more readily. More to the point, I have several apps in mind and/or under development that operate within a directory. Several apps will limit directory traversal operations. Some will want to see a variety of listings - filtering files and directories in various combinations based on keywords or categories. Presenting history to see what's changed and when. Viewing change notes. All pretty normal shell-stuff. It'll only be worth it if it can be made faster and more intuitive without sacrificing stability. I'll keep you posted...

I could have called out to unix from Lisp, but it was just so easy to write them in Lisp, I couldn't help myself. More to the point, they do that little extra that the apps need. If it turns out to be a mistake, I'll fall-back to call unix. But for now,
(with-sinister-laugh "They are under my control!")

In a little while, I'll post the code for the directory manipulation (right after this paragraph.) Then really, I gotta get back to the charting package. The tools I've written in the last couple of days should make it easier to finish. Course, I reserve the right to digress into another tangential tool development episode if there's a shot at increased productivity.

Incremental Development, Lisp and Small Hemlock Notes

Reworked the menu structure for Hemlock Apps on the train last night. Nothing significant, just a minor change to storage. Also, the first implementation built static menus and there's a need for some menus to be dynamic, so I sketched a solution to make them dynamic if desired.

Gave some thought to walking ASDF definition files and parsing lisp code to extract content for presentation. Parsing the dot asd file should be pretty straightforward, or so it seems at first blush. Parsing lisp code has been done, so I'm going to grovel through sources to find code that I can crib. Might ask on c.l.l first.

Last night, I wrote some code using interactive development technique. Built a small app to use for doc finding and viewing. I kind of want my docs to be easy to find, access and edit. Plus, I want to be able to see which files were edited - when and why. I spent too much time grepping for a file that I hadn't edited in a while and couldn't remember which directory it lived in and decided to start to address that morass of insanity.

Man, was it weird to work incrementally. Just stubbed and built functions line-by-line. Thought of what something did and made sure each part did what it was supposed to before any other code was written. This is really easy in Lisp because the functional model supports it well. Plus the interactive nature of the Lisp environment (REPL) favors rapid development.

Actual coding went like this: Built the app framework, with a couple of menus. No content in any functions except the basic Hemlock code to launch the app. Ran it. It brought up a window with menus. Menus were there, but were connected to stub functions. Verified the menus were selectable and didn't report an error on selection.

Then, gradually added content. I started by adding print commands when I began editing a function. Then, verified that, when the function is called, the print call output was correct. Then, converted the print call into a function call. Repeated the test until the code did what it was supposed to.

Learned the concept from The Mythical Man-Month by Fred Brooks (Harlan Mills - incremental development.) Problem with software is it's just easy to riff off a function or two, then start debugging. It doesn't seem like a problem, because the logic is "out there" when it's written in one fell swoop.

That's how I've written code for a long time. And I've got the furrowed brow from debugging the mess and carving in functionality that was needed IMMEDIATELY after "finishing".

But last night it became clear that building code incrementally reduces stress. I felt like I was cheating - wasn't I supposed to iterate over the logic a gazillion times until I randomly, hopefully, poorly finished the algorithm?

Apparently not.

When I finished for the night, I left code in a working state - knowing that the code that was there was stable, too. It was also well-documented. Too weird.

And one function is going to replace a heavily used directory traversing algorithm I'd written a long time ago that I'd been hesitant to rework because it worked, sort of, kind of, well enough. In ten minutes it was rewritten. Best part is it makes a hack I've been using even easier to use. Kind of amazing how quickly a mole-hill becomes a mountain.

I'm going to write a more detailed post on incremental development, what I did and how it progressed. It was so much fun to do, that it'd be interesting to see if that sense of fun comes across in an article.

Tuesday, July 22, 2008

Hemlock App Menus - Feature Request

Build some menus and the next thing you know, you're supporting them for the rest of your life...

Feature request, huh? Well, that didn't take long...was using the new menubar/menus scheme with Ed Vecto commands, inserting Vecto commands into a lisp function when I decided to extend the Ed Vecto app to add my Vecto extensions and customizations.

Ok, so now the Hemlock apps menus would need to be open so another app could append a menu to a menubar. Not a problem. (Could be more than append. I'm taking a kiss approach to coding these days, so I'll let the interactions define the scope.)

Before adding the code to modify a defmenubar, I built some defmenu/definsert forms for my vecto libs. Got bored walking my code a line at a time, copying and pasting function calls and their args. So I added definsert to the set of menus and made inserting definsert calls easier. But it was still tedious.

That got me thinking about how nice it would be if there was a menu-builder app that would parse my lisp forms for the files in an asdf package. The app would, after parsing, make the forms available (with args) so I could just pick and choose which lisp forms to make available to add to a menubar or menu or associate with a function (eg. definsert.)

I'll do some sketching on the train today and see how much work that'd be to build.

Anaphoric Macro - A Small Extension

Yesterday, while writing up a function, I used a combination of anaphoric macro and menu selection. A short time ago, I'd changed this same idiom from this:

(let ((res (select-menu items)))
(when res
(menu-item-dispatch res)))

To this:

(awhen (select-menu items)
(menu-item-dispatch it))

And I looked at the anaphoric form and said, that's not (awhen (select-menu ...)) that's (amenu ...)

(defmacro amenu (items &body body)
`(awhen (select-menu ,items)
,@body))

(amenu items
(menu-item-dispatch it))

That's better.

Monday, July 21, 2008

Quick Update - Hemlock App Menus

It took more time to write the last blog entry than it did to make the menus work. Added all the Ed Vecto menus and tested them. All forms insert fine - with a couple of exceptions; the forms that have keywords or body parameters aren't parsed yet. Going to add my Vecto extensions and charting calls.

Added some lisp forms and ran into limitations. The menubar/menu/insert forms work for simple, straightforward data entry. More complex forms don't work.

For some Lisp forms, eg. defclass, it'll be necessary to construct a dialog interface that provides the means to grow forms when there can be multiple entries. Or, for more complex structures like loop, the interface will have to accommodate the myriad options.

Already have an app/interface (data entry code) for Lisp forms. The app simply makes it easy to select a lisp function/macro call for insertion at the point without args. While it speeds up data entry, only a few forms have the extra juice (custom functions with interface code) to add more structure to a call. Will reconcile the new definsert and defmenu's with Lisp forms and, where needed, extend the interface to make inserting complex forms easy.

Won't update all the Lisp forms right away. Will bring over those that I frequently use and gradually bring the rest in over time.

Sunday, July 20, 2008

Adding Better Menu Structure to Hemlock Apps

In my last post, I documented some interface code that I was playing with for Hemlock, the Common Lisp Editor. I'd written two macros, 'definsert and 'defchoice for the purpose of creating functions that provided a means to quickly insert lisp forms at the point while making it possible to present custom options for selection on a per form basis.

Friday night, I had a little time to play with the code and thought it was a good first-pass. I didn't get much time to code this weekend, but I kept thinking about what I'd written.

Saturday, I gave some thought to creating a set of menu items for the Hemlock Vecto editor/utility (I'm calling Ed Vecto.) It was easy to add code to definsert to construct a menu. But, the menu was just a blob that screamed for better structure (categories.)

On top of the weak menu structure, defchoice and definsert were disjoint and defchoice wouldn't work if more than one slot in a definsert needed customization. Better to use the same approach as defstruct or defclass and create a set of slots with customizable behavior, eg. initform/initarg forms. That way, defchoice could be eliminated.

Want the means to make a menu selection and dispatch to a function - in this case to perform data collection and insertion of a form.

While Hemlock apps had menus, the structure wasn't well defined - simply trees. Now was as good a time as any to update the menu structure. Created defmenubar and defmenu. Defmenubar defines the menubar categories. Defmenu maps categories to subcategories.

The basic structure for menus and insert commands looks like:

(defmenubar canvas graphics-state paths text) ; Vecto categories.
(defmenu canvas with-canvas clear-canvas save-png save-png-file) ; Vecto canvas funcs.

(definsert with-canvas (&key width height) &body body) ; this form isn't parsed yet.
(definsert clear-canvas)
(definsert save-png file)
(definsert save-png-stream stream)

(defmenu graphics-state set-line-cap set-fill ...) ; more options available.
(definsert set-line-cap (style :initform '(:miter :bevel :round)))
(definsert set-fill (color :initform choose-color))


Not sure about whether the keyword :initform will be used, but it's a reasonable placeholder for this article.

Will have to provide a mapping from defmenu slots to the definsert forms - probably use a keyword in defmenu (since defmenu creates the two elements of a menu the name and dispatch function and definsert creates the function I want to call when a menu is selected in Ed Vecto.) Adding a keyword option will make defmenu look like:

(defmenu graphics-state (:prefix 'insert) set-line-cap set-fill)


The new additions will make Hemlock apps cleaner and eliminate the trees I've been relying on for menus. Wanted to address Hemlock app menus for a while now, so it's good to take a shot at cleaning them up. Hopefully, this'll be a decent approach.

Will be upgrading Vecto to the latest revision (1.3.1) and adding the new calls to Ed Vecto (and playing with the new features - gradients and text-paths.) After testing Ed Vecto, will get back to the charting app.