Monday, July 6, 2009

An experiment in object-oriented AutoLISP

It has taken me a while to grok object oriented programming. But having sort of groked it, I kind of would like to be able to do a little bit of it with my old friend, AutoLISP (or Visual LISP). Why would I want to do that? Well, suppose you want to write a function to make pancakes, and one morning you decide to make blueberry oatmeal pancakes in the shape of your kids' initials. Without object oriented programming, you might make the pancakes like this:


(make-pancake (list "blueberry" "oatmeal" "brown sugar") "Sally")
(make-pancake (list "blueberry" "oatmeal" "brown sugar") "Billy")
(make-pancake (list "blueberry" "oatmeal" "brown sugar") "Joey")
(make-pancake (list "blueberry" "oatmeal" "brown sugar") "Elizabeth")
(make-pancake (list "blueberry" "oatmeal" "brown sugar") "Marci")


That seems repetitive.

Another way you might do it is to use a global (external) variable for ingredient values inside the function. Then you might make the pancakes like this:


(setq ingredients (list "blueberry" "oatmeal" "brown sugar"))
(make-pancake "Sally")
(make-pancake "Billy")
(make-pancake "Joey")
(make-pancake "Elizabeth")
(make-pancake "Marci")


That seems less than elegant. What if you want to vary the ingredients, not the kids' names. I guess you could make all the variables in (make-pancake) global. But then you have to remember to make them local in the function that calls them, or leave them totally global and exposed to conflict with other applications.

Then (tada!) there is the object oriented way, or at least my OO-ish hack in AutoLISP. Consider this just an idea. Take it for what it's worth. Working example to follow.


(new-PancakeMaker 'PMTuesday)
(put-property 'PMTuesday "INGREDIENTS" (list "blueberry" "oatmeal" "brown sugar"))
(put-property 'PMTuesday "NAME" "Sally")
(PancakeMaker-make-pancake)
(put-property 'PMTuesday "NAME" "Billy")
(PancakeMaker-make-pancake 'PMTuesday)
(put-property 'PMTuesday "NAME" "Joey")
(PancakeMaker-make-pancake 'PMTuesday)
(put-property 'PMTuesday "NAME" "Elizabeth")
(PancakeMaker-make-pancake 'PMTuesday)
(put-property 'PMTuesday "NAME" "Marci")
(PancakeMaker-make-pancake 'PMTuesday)


Here is a working example. This example installs (copies) files:


;;;First the functions
;;; A small experiment in object oriented AutoLISP

(DEFUN
NEW-INSTALLER (OBJECTNAME)
(SET
OBJECTNAME
'(("SOURCEPATH") ("INSTALLPATH") ("NFAILURES" . 0))
)
)
(DEFUN
PUT-PROPERTY (OBJECT PROPERTY VALUE)
(SET
OBJECT
(SUBST
(CONS PROPERTY VALUE)
(ASSOC PROPERTY (EVAL OBJECT))
(EVAL OBJECT)
)
)
)
(DEFUN
GET-PROPERTY (OBJECT PROPERTY)
(CDR (ASSOC PROPERTY (EVAL OBJECT)))
)
(DEFUN
INSTALLER-INSTALL (OBJECT FILEPATH / SOURCE DESTINATION)
(SETQ
SOURCE
(STRCAT (GET-PROPERTY OBJECT "SOURCEPATH") FILEPATH)
DESTINATION
(STRCAT (GET-PROPERTY OBJECT "INSTALLPATH") FILEPATH)
)
(IF (FINDFILE DESTINATION)
(VL-FILE-DELETE DESTINATION)
)
(IF (NOT (VL-FILE-COPY SOURCE DESTINATION))
(PUT-PROPERTY
OBJECT
"NFAILURES"
(1+ (GET-PROPERTY OBJECT "NFAILURES"))
)
)
)
;;;Second the usage
(NEW-INSTALLER 'KIPINSTALL)
(PUT-PROPERTY
'KIPINSTALL
"SOURCEPATH"
"R:\\Updates & Installs\\KIP\\HDI 2010\\"
)
(PUT-PROPERTY
'KIPINSTALL
"INSTALLPATH"
(STRCAT (VLA-GET-PATH (ACAD-OBJECT)) "\\")
)
(FOREACH
FILEPATH
'(
"drv\\kip_kawpdft.exe"
"drv\\kip_kipzip.exe"
"drv\\kip10res.dll"
"drv\\polarziplight.dll"
"drv\\kip10.hdi"
"drv\\kip10.drc"
"Help\\drv_kip.chm"
)
(INSTALLER-INSTALL 'KIPINSTALL FILEPATH)
)
)
)
(COND
(> (GET-PROPERTY 'KIPINSTALL "NFAILURES") 0)
(ALERT
(PRINC
(STRCAT
"There was a failure in installing the kip drivers from\nR:\\Updates & Installs\\KIP\\HDI 2010"
)
)
)
)


I'd love to see some improvements on this. Let me know what you can do.

2 comments:

P-Y Delens said...

Bonjour.
Very interesting !

But what are the single quotes present in the code?

Have you more examples since then?

Thanks on forward

PY

Thomas Gail Haws said...

Hi, PY. First the what. Then the why. I'm very clear on the what. Not so sure I remember the why (document, document, document!).

WHAT:

'billy = (QUOTE billy)

(setq billy 1) = (set 'billy 1) = (set (quote billy) 1)

So I think we can say if we call a function with 'billy as an argument, we are handing the function the name billy, and not the current value of billy.

WHY

So why did I use a symbol name 'billy instead of a string "billy"? Hmm. Head scratcher.

Here's what I think: When I invoke (new-PancakeMaker 'PMSaturdayMorning) or (new-Installer 'KipPlotter), they create a new global AutoLISP variable for that "instance" of the "object" in the current drawing's space. You could inspect those "instances" by typing !PMSaturdayMorning or !KipPlotter. I don't have enough experience with this type of AutoLISP programming (I haven't pondered, used, or discussed it enough) to know whether that is better than the alternate method which is as follows:

Alternately, you could set up your object strategy so you invoke (new-PancakeMaker "SaturdayMorning") or (new-Installer "KipPlotter") and they create a new associative entry in their respective list of instances, and you would inspect them by doing something like this:

(assoc "SaturdayMorning" PancakeMakersList)

or

(assoc "KipPlotter" InstallersList)

Maybe the second way is "better". And maybe I was just being clever or cute with the symbol name ('billy) method, which is a big no-no in my book.

What do you think?