The FoxPro programming language is composed of command and function words. You use these for simple interactive commands and for programs, simple and complex. It's also possible to combine the existing functions and command words, package and label them as a new function, then use that new function just like the built-in functions.

This make-your-own-tool capability is a fundamental feature of most programming languages. I'll come back to the uses and advantages of making functions. First, let's make a really simple one.

The first function we'll write doesn't really add anything new to FoxPro, though that's the usual reason for writing one. The function noDot() returns a string with all the periods stripped out. It's equivalent to, but shorter than the second command.

*The function we'll write will be used like this: ? noDot (address)

It is equivalent to: ? strtran (address, ) Example code FUNCTION noDot Parameters cString RETURN strtran ( cString, )

That's it. Type three lines, save it, and you have a new function for FoxPro. The manual calls them UDFs, or User Defined Functions. The next function we'll write doesn't add anything new to FoxPro either, but a handy substitute for its hard-to-memorize-and-type equivalent.

The function rSplit() returns the part of a string that's on the right side of a separating character.

The function we'll write will be used like this: ? rsplit (name) It is equivalent to: ? substr (name, at name) )

This one is a bit more complicated. Also, we'll write it in several passes, adding and explaining features, improvements along the way.

First version FUNCTION rSplit

parameters cString nSplitPos = at (',' , cString) RETURN alltrim (substr (cString, nSplitPos + 1

Most of the time, the comma is the separating character in the string we want to split. Our function, as written, assumes that. However, if we want to use this function to split apart a string based on another character, we'd have to edit the code to change the quoted character in the second line, where the at() function finds its location.

When we were done with that odd, special case, we'd have to remember to change the code back to the original version, which assumed a comma as the separator.

Our second revision makes a better way of doing this: When calling the function, we specify the string to split and the separating character.

Second version: add a parameter for separator FUNCTION rSplit *usage: rSplit (name, ) parameters cString, cSeparator jnsplitPos = at (cSeparator, cString) RETURN alltrim (substr (cString, nSplitPos + 1

But this still isn't as smart as it should be. We know that most of the time, the comma is the separating character; our function should reflect that. So we'll make the comma the default choice, unless another separating character is supplied to the function.

We can handle this default-or-optional feature if we can tell the function to find out how many parameters were passed to it. That's what the parameters() function does: counts how many parameters were supplied to the current function. If rSplit() has two parameters supplied to it, then a separating character has been explicitly defined. If it has one parameter, that means the function has to explicitly fill in what we let the user imply: the separator is a comma.

Third version: make separator DEFAULT to a comma FUNCTION rSplit usage: rSplit (name) rSplit (offenses, parameters cString, cSeparator if parameters ( ) = 1 cSeparator = 'i' endIF

nSplitPos = at ( cSeparator, cString) RETURN alltrim (substr ( cString, nSplitPos + 1 * end of rSplit ( )

The function seems perfect now: It's as smart and simple as it can be. Still, we're going to make one more improvement.

It may not even seem like a real improvement or a necessary one. It probably seems a bit arcane an attempt to guard against the improbable.

I'm assuming three things about programs: 1) Nothing is improbable. 2) If the improbable happens, make sure that either there is no damage or there is a graceful way out. 3) The last improvement involves a significant programming concept.

Inside this function we are creating a new variable, nSplitPos, by assigning a value to it. We want to be able to use this new function in any new or old FoxPro script we create and we have to ensure that there are no surprises.

We may think that it is not very likely that there is a variable with the name nSplitPos already alive and working somewhere else in the currently running FoxPro script.

But if there were already such a variable, the assignment statement in our function would overwrite the current value held by nSplitPos.

Anytime your code does something you didn't intend or, worse, weren't even aware of, that's bad. While it may not be immoral, it can cause you real grief trying to find and fix the damage.

To make sure our function couldn't possibly interfere with any other variables, we'll add the statement:

private inSplitPos

This hides any other variables outside the function so it can't interfere with them.

Fourth version: make sure variables created by the function do not clash with any others.

FUNCTION rSplit parameters cString, cSeparator private nSplitPos if parameters ( ) = cSeparator = ',' endIF nSplitPos = at (cSeparator, cString) RETURN alltrim (substr (cString, nSplitPos + 1 * end of rSplit ( ) Richard Mullins can be reached at (573) 882-2127, or send e-mail to richard@nicar.org