Harry's Code Class: Tips for Programmers (March 2007)
26 Mar, 2007The How-Tos of Well-Structured LISP
A fundamental requirement of good programming is breaking down code into readable, useful functions.
The craft of programming AutoCAD with Visual LISP involves creating readable, useful functions in a clear, structured routine -- which is part science and part art. This article will introduce the concepts of structured programming and function types as applied to Visual LISP.
Command Functions
Let's start with a look at the general notion of function types. A function is a program you create in Visual LISP. The best known type is the command function, which is one that contains C: in the function name. (C: stands for Command, not the C: drive on your computer.) Command functions can be activated from the AutoCAD command line by typing in the name of the function without the C: prefix. That makes them useful when customizing buttons, pull-down menus, flyouts, tablets and so forth inside AutoCAD's menu system. All your menu system needs to do is process the defun statement containing the C: function name and your custom command becomes available.
An example of a command function can be seen in Example 1. Note the command function name with the C: following defun in the first line of the program listing. By the way, this sample command function is not just a simple "hello, world"-style example. It is a bit more complex and represents a standard structure for many programs. Let's pick it apart line by line.
Example 1: Sample Command Function
1. (defun C:MyCommand ( / Something Answers)
2. (setq Something (GetSomething))
3. (if Something
4. (if (setq Answers (DoItTo Something))
5. (ReportIt Answers)
6. (prompt "\nUnable to deduce answers.")
7. )
8. (prompt "\nNothing to work with.")
9. )
10. (princ)
11. )
Line 1. The defun expression is the outside wrapper of the function definition. Following the defun symbol are the function name and a parameter list. The function name is C:MyCommand and can be referenced as a command function named MyCommand once it is loaded inside AutoCAD. The last part of this line is the parameter list. Symbols listed inside this list are local variable references inside the function. Symbols displayed before the slash have an initial value supplied by the calling function. Symbols displayed after the slash have an initial value of nothing, or nil. Command functions should only contain symbols after the slash, if any. If there are no parameters, an empty list must be supplied (open and close parentheses pair).
Line 2. Setq is used to set the value of a variable symbol. In this instance setq will take the result of GetSomething and set up a reference via the symbol Something. GetSomething is a function call to another defun of your creation. What does it do? Well, it gets something in the form of input. That could be text, numbers, entities or whatever your application needs in the form of input. When this line finishes evaluation, the value returned from the function GetSomething is available in Something. Thus it is important in the programming of GetSomething to make sure it returns something.
Line 3. This line checks the value of Something. If the value is not nil, then line 4 will be evaluated. Otherwise, if Something has nothing in it, line 8 will be evaluated. Evaluated means that the line in will be presented to the LISP system and run to produce a result.
Line 4. Line 4 is run only if Something evaluated to a non-nil answer in Line 3. That means that there is something in Something. This line demonstrates a programming aspect of LISP called nesting. To figure out what is happening here you need to find the innermost pair of parentheses. In this case it is the expression (DoItTo Something). DoItTo is a symbol name of a function to be defined later in our programming. It is like the input function, except in this case it is the processing function. So now, DoItTo is a function that accepts Something, then does something with the input data and returns a result that is set into the symbol Answers. Setq returns the value of what was set into the symbol. Setq serves as the predicate (or test expression) in the surrounding IF expression. When Answers has a non-nil value, line 5 is evaluated next. Should Answers be nil, line 6 is evaluated.
Line 5. If line 4 resulted in values being stored in Answers, this line of code will run.
The last step in a normal program is to report the results. In this line of code, a function named ReportIt is called with the value of Answers. ReportIt is another function we need to define later. When this line is finished, control proceeds to line 7.
Line 6. Often called the else or otherwise expression of a conditional expression, this prompt sends an error message to the command line indicating that the function DoItTo did not return a result. If the result of the DoItTo function call is nil, then the if expression in line 4 sends program control to this expression so we can report the problem. Now we go to line 7.
Line 7. This is the end of the if expression started in line 4.
Line 8. This line is the else expression associated with the test started in line 3. This line of code will run only if nothing was input.
Line 9. The end of the if expression started in line 3.
Line 10. The (princ) expression can be used at the command functions to suppress the result of the function. Because a command function is normally returning to the command line, there will be times when you do not want the result of the last expression evaluated in the function displayed. This applies only to command function applications and not to most other functions you will write in LISP where an answer from the function is expected.
Line 11. The end of the defun expression started in line 1. This is the closing or balancing parenthesis of our program.
Processing Functions
So what does that program do? Nothing, until we add the three function definitions shown in Example 2. These three functions were invoked in the command function and can be thought of as subroutines that return an answer. The results of the first two -- the input and processing subroutines -- may be nil or a value. In this case, the input subroutine will return nil unless an integer is input at the Command line. The processing function will result in nil if the input is not a number.
Example 2: Do Something
(defun GetSomething ( )
(getint "\nEnter an integer number: "))
(defun DoItTo (aNumber)
(cond
((numberp aNumber) (+ aNumber 5))
)
)
(defun ReportIt (aNumber)
(prompt "\nAdd 5 to your input and you get ")
(princ aNumber)
)
To test these routines in AutoCAD, start Visual LISP by typing VLIDE at the Command prompt in AutoCAD. Load the provided source code into the editor, then select the Load Active Edit Window icon to process the function definitions. Return to the AutoCAD window and select the text window (function key F2) for display. Type Mycommand at the Command line, then supply a number for the prompt.
Programming 1-2-3: The Value of Structure
Are you ready to take a crack at writing a simple routine? The easiest way to get started creating structured applications is to identify the input, process and output you desire. Those are the three main components, or steps, of nearly any structured routine. As a simple example think about a program that
displays the slope from one point to another. The input step would request the two points. The processing step would compute the slope from the point data. The output step would present the results of the calculation.
Cautionary Word About Variables |
Variables that reach outside a function are one thing that can get you into trouble unless you develop a very basic discipline in LISP. That discipline is to use localized variables, which are accomplished in Visual LISP by placing the symbol names after the slash mark in the parameter list. Symbols that are declared in this area of a program become local to the function. They are visible throughout the function as well as inside any called functions. But once that function is completed, the symbol reference reverts to whatever it was before the function was started. Global and local symbol use is a subject for a future newsletter; I just wanted to mention the concept at this point because it relates to the programming of structured applications. |
For some complex problems, you might need to mix things up, such as getting a little input, doing a little processing, then getting a little more input and so forth. But for the most part, you should be able to divide a programming problem into these three main sections. If you can do that, and if you can follow the basic model shown in these nonsense examples, you are well on your way to developing programs you can read and maintain.
Structured programs provide a compartmentalized approach to debugging. If you program everything in a linear fashion, as a single routine, it takes longer to locate and fix bugs, change or upgrade the code. Using a structured approach, you can develop each subroutine as an isolated module, creating a simple test program that sets up the input and reports the output. That way you can proceed to other modules in a complex application with the confidence that your previous modules are functioning OK.
I realize that I am a bit old school when it comes to programming -- I did start with keypunched cards and green-striped paper output. But back in the dark ages I learned some general rules of thumb for structured programming that still apply nicely to any programming effort. (For more basics and a glossary of terms, refer to the February 2007 edition in the Harry's Code Class newsletter archives.)
- Try to keep function definitions less than 50 lines long. Anything more cannot be displayed on a single sheet of paper. When programming in LISP, I believe the fewer lines in a function definition, the better. Otherwise, you're just getting carried away with nesting expressions.
- Use descriptive names for your functions and variables. Nothing makes a program more readable down the road -- by you or by other users -- than descriptive names. No one will need to guess about the nature of a variable if the name says it all.
- Add comments that actually mean something related to the programming or that promote a fluid reading of the code. This is something that you will not really appreciate until you are trying to make sense of some code you wrote in the past.
In closing, I'd like to share an ancient programming proverb: "All programs must some day be maintained, even the shorter ones."
Until next time, keep on programmin'!