Home Web Design Programming Fairlight CMI Soap Box Downloads Links Biography About... Site Map

The Holmes Page Providing Default Parameter Values

CA-Clipper Opportunities Tips and Tricks Networking Internal Errors Source Code CA-VO



BACK TO
CA-CLIPPER
TIPS & TRICKS

A primary rule of programming with CA-Clipper (and with other languages) is to make functions "smart". In CA-Clipper, this often means that useful default values should be supplied for function parameters. 
A classic example of a smart function is the dbedit() function. It was designed to accept many parameters (as many as twelve), all of which are actually optional. Here is the definition of the function, from the Norton Guides on-line manual that comes with CA-Clipper:
    dbedit(nTop, nLeft, nBottom, nRight,
           acColumns,
           cUserFunction,
           acColumnSayPictures,
           acColumnHeaders,
           acHeadingSeparators,
           acColumnSeparators,
           acFootingSeparators,
           acColumnFootings) --> NIL
That is quite a list of parameters, but you can call dbedit() as simply as this:
    use CUSTOMER new
    dbedit()
    close CUSTOMER
 
This provides a browse window that fills the screen and displays all of the fields of the table. The nTop, nLeft, nBottom, and nRight parameters would automatically default to 0, 0, 24, and 79 (assuming a standard VGA screen size). If you want to display the browse in a smaller window, you can pass screen coordinates as the first four parameters:
    dbedit(10, 10, 20, 70) 
This browse still displays all of the fields, so if you want to limit the display to certain fields in the table, you can pass an array of field names:
    dbedit(10, 10, 20, 70, {'NAME','ADDRESS'} ) 
If you want to skip parameters (thus falling back to the default values), you still need to specify the correct number of commas:
    dbedit( , , , , {'NAME','ADDRESS'} ) 
Without going any further into the dbedit() function, you can see that optional parameters and smart default values are very useful in removing some of the complexity from your source code. 

One of your goals when writing functions is to make it easier to use them. So to implement smart default values for parameters you typically check the parameters to see if they have been assigned a value. This is done by comparing them to the special value NIL. The examples that follow illustrate the source code as it might have been written inside the dbedit() function.
    function dbedit ( nTop, nLeft, nBottom, nRight, ... )
       ...
       if nTop == NIL
          nTop := 0
       endif
       if nLeft == NIL
          nLeft := 0
       endif
       if nBottom == NIL
          nBottom := maxrow()
       endif
       if nRight == NIL
          nRight := maxcol()
       endif
       ...
    return ...
The dbedit() function also checks the data type of the parameters, since it is possible to pass a string where a number is expected. The valtype() function is used to see if the data type of the parameter matches what is expected.
    function dbedit ( nTop, nLeft, nBottom, nRight, ... )
       ...
       if .not. (valtype(nTop) == 'N')
          nTop := 0
       endif
       if .not. (valtype(nLeft) == 'N')
          nLeft := 0
       endif
       if .not. (valtype(nBottom) == 'N')
          nBottom := maxrow()
       endif
       if .not. (valtype(nRight) == 'N')
          nRight := maxcol()
       endif
       ...
    return ...
This code basically reads: "if the nTop parameter is not a number, then set it to 0". Similarly for the rest of the parameters. 
In order to make this source code easier to read, it is a good idea to create a new command in the CA-Clipper language. This is done by harnessing the power of the CA-Clipper pre-processor (which translates your source code according to specified rules before it is compiled). Here is the definition of this new comand: 
#xcommand DEFAULT NUMERIC <v> TO <x> => ;
          if .not. (valtype(<v>) == 'N') ; <v> := <x> ; end
 
And here it is (as it might have been used) in the dbedit() function:
    function dbedit ( nTop, nLeft, nBottom, nRight, ... )
       ...
       default numeric nTop    to 0
       default numeric nLeft   to 0
       default numeric nBottom to maxrow()
       default numeric nRight  to maxcol()
       ...
    return ...
You can see that this code is much clearer than before. 
I have created several of these commands, covering each of the data types in CA-Clipper. The commands are contained in a file called DEFAULT.CH, which is included at the top of most of my source code files. 
*===========================================================
* File Name   | DEFAULT.CH
* Description | Commands for providing default values.
* Author      | Greg Holmes, gregh@ghservices.com
*===========================================================

#ifndef _DEFAULT_CH_
#define _DEFAULT_CH_

#xcommand DEFAULT <v> TO <x> [, <vn> TO <xn>]  => ;
          if <v> == NIL ; <v> := <x> ; end        ;
          [ ; if <vn> == NIL ; <vn> := <xn> ; end]

#xcommand DEFAULT ARRAY      <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'A') ; <v> := <x> ; end

#xcommand DEFAULT BLOCK      <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'B') ; <v> := <x> ; end

#xcommand DEFAULT CODE BLOCK <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'B') ; <v> := <x> ; end

#xcommand DEFAULT CODEBLOCK  <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'B') ; <v> := <x> ; end

#xcommand DEFAULT CHARACTER  <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'C') ; <v> := <x> ; end

#xcommand DEFAULT STRING     <v> TO <x>      => ;
          if .not. (valtype(<v>) $ 'CM') ; <v> := <x> ; end

#xcommand DEFAULT DATE       <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'D') ; <v> := <x> ; end

#xcommand DEFAULT LOGICAL    <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'L') ; <v> := <x> ; end

#xcommand DEFAULT MEMO       <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'M') ; <v> := <x> ; end

#xcommand DEFAULT NUMBER     <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'N') ; <v> := <x> ; end

#xcommand DEFAULT NUMERIC    <v> TO <x>      => ;
          if .not. (valtype(<v>) == 'N') ; <v> := <x> ; end

#endif
Notice that the end statement is used above, rather than the preferred endif statement. The end spelling can be used in place of endif, enddo, and endcase, but the shorter form is not as clear as the full spelling. In other words, when multiple end statements are encountered (as would be the case with nested loops), it is hard to tell them apart. 
However, in this context the end spelling is necessary in order to overcome a strange bug in the CA-Clipper pre-processor. The bug causes source code after an endif to be ignored when processing #xcommand translations. 
Here is a simple example showing the use of the new commands: 
*===========================================================
* File Name   | TEST.PRG
* Description | Example with default parameter values.
* Author      | Greg Holmes, gregh@ghservices.com
*-----------------------------------------------------------
* Routines    | Main ( )
*             | SaySomething ( [cMessage], [nRow], [nCol] )
*===========================================================

#include "Default.ch"        // Custom commands.

*-----------------------------------------------------------
* Function    | Main
* Purpose     | The entry point for the test program.
* Parameters  | none
*-----------------------------------------------------------
procedure Main ( )

   SaySomething()            // Displays the default text.
   SaySomething( , , 0)      // Default text at column 0.
   SaySomething("Bye!")      // A different message.

return

*-----------------------------------------------------------
* Function    | SaySomething
* Purpose     | Display a simple message on the screen.
* Parameters  |[pcMsg] - optional text, default "Hello".
*             |[pnRow] - optional row, default middle row.
*             |[pnCol] - optional col, default middle col.
* Returns     | NIL
*-----------------------------------------------------------
function SaySomething ( pcMsg, pnRow, pnCol )

   default character pcMsg to "Hello"
   default numeric   pnRow to int(maxrow() / 2)
   default numeric   pnCol to int(maxcol() / 2)

   @ pnRow, pnCol say pcMessage

return NIL


Home Web Design Programming Fairlight CMI Soap Box Downloads Links Biography About... Site Map

Site Map Send comments about this site to Greg at gregh@ghservices.com
All pages copyright © 1996-1999 GH Services™   Created 1997/07/15   Last updated 1999/09/30
All trademarks contained herein are the property of their respective owners