Simple Extensions. (Exact Comparisons, Name Precedence, Select 0, Alias Functions, Call by Reference and Value, and Other Clipper Extensions)
Simple Extensions. (Exact Comparisons, Name Precedence, Select 0, Alias Functions, Call by Reference and Value, and Other Clipper Extensions)
(exact comparisons,
name precedence,
SELECT 0,
alias functions,
call by reference and value, and
other Clipper extensions)
Simple Extensions
In the last few articles we've concentrated on memoedit and dbedit. These two functions greatly extend the dBase language by allowing user
interfaces that are almost impossible to create with dBase, FoxBASE+, and Quicksilver.
In this article we'll look at some Clipper extensions to the "host" language, as it's referred to by most books on relational databases. These insightful
extensions will make your programming life a little easier. You already know that Clipper has UDFs, valid clauses, arrays, and FOR...NEXT loops. So
this article focuses on some lesser known extensions.
Exact comparisons
When you compare two character expressions as follows: IF a = b the result depends on whether EXACT is On or OFF. If EXACT
is ON, True is returned when a and b have the same value and are the same length.
IF EXACT is OFF, TRUE is returned if a and b are the same up to the length of b.
The trouble with EXACT is that programmers often forget to turn it on or off as required, causing unexpected program errors.
(There are many other subtleties to SET EXACT that I won't go into here.)
Clipper provides an elegant, simple solution to this with the operator, ==. These two equal signs simply mean, "do an exact
comparison." It's the same as doing a SET EXACT ON and using =. In regular dBase, the = operator behaves differently based
on the current EXACT setting. I consider this absurd. Imagine debugging an application and wondering at every = whether
EXACT was on or off. With double equal, look at how much clearer the following code is:
USE address
INDEX ON upper( lastname ) TO names
look__for = "S"
SEEK look__for
DO WHILE upper(lastname) == "S" .AND. !eof()
? lastname,
..
SKIP
ENDDO
It's clear that we're doing an exact comparison. To do a "not equal" exact comparison, use: IF !(name == "")
I've found that a wrong setting of SET EXACT is one of the most common sources of problems in Clipper / dBase code.
Programmers seem to leave it off, without ever worrying about it. This is sloppy programming that isn't often discovered until
an application has been running for a while, and just the right set of data has been encountered to cause it to fail.
Ed.'s note: If you must use SET EXACT, you can determine its setting with this simple user defined function:
FUNCTION exact
RETURN IIF(("AB" = "A"),.F.,.T.)
If you have a memory variable and a field with the same name, the field is given priority over the variable. Usually that's what
you don't want. To overcome this, many programmers use the M override, as in: M -> var_name to give priority to the memory
variable. This is fine for little dBase programs whose main job is editing databases. They don't use many memory variables, so
prefixing the few that do with the M override (M->) is not much of a concern. But, as soon as you begin to write more
sophisticated programs, you can't avoid using a large number of variables. To me, it's a pain to have to precede every
reference with M->. I usually refer to the memory variable. It's rare that you'd refer to a field name except when loading
variables to and from the current record.
Once again, Clipper offers a well-considered little extension to save the day. If you compile your program with the -v flag,
memory variables get priority over field names. That's all that's required. Where you need to refer to the field name directly,
prefix it with the database's alias name. This also makes the code more readable.
SELECT 0
Another extension that I made a fleeting reference to in the first article of this series concerns the SELECT command. I find
nothing more unreadable than: SELECT 5 stuck in the middle of a piece of code. What does 5 tell me? Without backtracking
through the code to the previous SELECT 5 / USE, it doesn't mean a thing.
In Clipper (and FoxBASE+), if you issue SELECT 0, the next available area is selected. The program doesn't care what the
number is. This is a system level feature that should have been kept hidden from the programmer. If you open your databases
with a SELECT 0 and refer to them by the alias name, you'll never need to use a hard coded selection area in your program.
Another extension is the select function. If you don't pass it a parameter, it returns the current area. This is invaluable in
generic functions that need to open a database, execute a function, then close it and return to the original area. The following
code shows this skeleton:
FUNCTION generic
PRIVATE save_sel
save_sel = select()
SELECT 0
USE ...
USE && close another
SELECT (save_sel) && reselect original area
RETURN VOID
You can also use the select function to check whether a given database is open. Again, this is useful in generic functions where
you need to use a specific database. If the database is already open, you don't want to reopen it. (Actually Clipper will let you
do this, and as long as you're only reading it, no harm will occur on a single-user system. It can lead to strange problems with
Novell though.) You can tell whether a database is open by passing the alias name to select(). It will return its select area if it's
open and a zero if it isn't.
FUNCTION generic
PRIVATE save__sel, we__opened__it
save_sel = select()
IF select(the_alias)
we_opened_it = .F.
SELECT (the_alias)
ELSE
SELECT 0
USE (the_alias)
ENDIF
. . .
IF we_opened_it
USE && we__close__it !!
ENDIF
SELECT (save__sel) && reselect original area
RETURN VOID
Alias functions
In "standard dBase" (whatever that means these days) any function that operates on a database, such as eof(), recno(),
lastrec(), etc., can only be used on the currently selected database. This is a real pain in the neck. It means that when you're
doing anything substantial with more than one database, you continually have to select and reselect areas just to check for
things like eof(). Clipper (and dBase IV to a certain extent) overcomes this by allowing you to use an alias with these functions.
That way, you can use them on any open database. (FoxBASE+ lets you use a work area number.) As an example, let's say
you want to append records from database a to database b. Without the alias functions you would write this as:
SELECT 0
USE b
SELECT 0
USE a
DO WHILE !eof()
SELECT b
APPEND BLANK
REPLACE field1 WITH a -> field1,;
field2 WITH a -> field2,;
...
fieldn WITH a -> fieldn
SELECT a
SKIP
ENDDO
Not a bad solution, but you have to issue two SELECT statements. The code gets worse when more than two databases are involved.
To use database functions on an alias, you enclose the expression in parentheses and use an alias expression to select it, just as you would a field
name. Using these functions, you can rewrite the preceding code as:
SELECT 0
USE a
SELECT 0
USE b
DO WHILE !a -> (eof())
APPEND BLANK
REPLACE field1 WITH A -> field1, ;
field2 WITH a -> field2, ;
...
fieldn WITH a -> fieldn
SELECT a
SKIP
ENDDO
We used another Clipper extension, SKIP ALIAS, to advance the alias database pointer.
Call by reference and value
For those of you who don't already know, "call by reference and value" refers to the relationship between the parameters. A subroutine
(PROCEDURE/FUNCTION) is called with the actual parameters. A subroutine is defined with the formal parameters. Simply put, if parameters are
passed by reference, any changes made to them by the subroutine also change the value at the caller's level.
If parameters are passed by value, their value is not changed at the caller's level.
Clipper passes parameters to PROCEDUREs and FUNCTIONs differently. By default, parameters to PROCEDUREs are passed by reference--they can be
changed. Parameters to FUNCTIONs, however, are passed by value--they cannot be changed. These default rules can be overridden. To pass
parameters to PROCEDUREs by value, enclose them in parentheses. Clipper will then regard them as an expression, which can't be passed by
reference (how could the procedure change the value of a * b for example?). To pass parameters to UDFs by reference, precede them with the @
symbol.
Arrays are the one exception to this. Array elements are always passed by value. Entire arrays are always passed by reference. This exception
applies to procedures and functions as well.
Fields are another story. They're passed just like memory variables, by reference to PROCEDUREs, and by value to UDFs. If you try to pass a field by
reference, you'll get a run-time error. So you'd best not do it! To pass a field to a procedure, enclose it in parentheses.
Getting rid of the macro
Macros are most needed where the language requires a literal name that cannot be an expression. For example, to use a file, the USE statement
requires a hard-coded name; it can't be an expression. You have to write: USE address; you can't write: USE "address".
I'd like to see all constructs like this disappear from the language, allowing expressions where constants are required (such as REPLACE
"field__name" rather than REPLACE field__name), but that's another story. Clipper has taken one step in this direction by allowing "file name
expressions" where file names are required. A "file name expression" is simply a character expression enclosed within parentheses. So, when you
need to open a database whose name is given by the memory variable "fname," instead of writing: USE &fname you can write: USE (fname).
SOFTSEEK, a SET that is either ON or OFF, can be used for relative seeking. With SET SOFTSEEK ON, a failed SEEK leaves the record pointer at the next
logical record in the index instead of at the end of the file.
If you're searching for SPENCE, for example, but we only have BROWN and TOWER, with SET SOFTSEEK ON the record pointer will be left at TOWER.
With SET SOFTSEEK OFF, it's left at the end of the file. To test whether the record was found, you would use the found function, since you can't rely
on !eof() anymore.
Let's say you want to process all records with a date > x. The only way to do this without using SOFTSEEK is to write:
SEEK x
DO WHILE date__field = x .AND. ! eof()
SKIP
ENDDO
DO WHILE !eof()
SKIP
ENDDO
SET SOFTSEEK ON
SEEK x + 1
DO WHILE !eof()
SKIP
ENDDO
(Editor's Note: dBase IV's SET NEAR command is similar to SET SOFTSEEK, but SOFTSEEK came first.)
Summary
This month we've looked at some Clipper extensions that can make your programming life a little easier--nothing earth-shattering, but certainly the
kind that allow cleaner, more concise, and more readable programs. If you have enhancements, send them in, and we'll publish the best of the lot.
Rick Spence was a member of the Nantucket development team for three years. He is currently working for his company, Software
Design Consultants, in Munich, West Germany. His book on Clipper, the "Clipper Programming Guide," is part of the Data Based Advisor
Series published jointly by Data Based Solutions, Inc. and Microtrends Books. Rick also writes a monthly column for "Reference(Clipper)."
He can be reached at Fidelio Software, Widenmayertrasse 6 / III, D - 8000, Munchen 22, West Germany.
http://www.accessmylibrary.com/coms2/summary_0286-9203466_ITM