They're Mysterious
They're Fun
They're Often Misunderstood
They're Code Blocks

by Thomas Leylan

Published in Greg Lief's Aquarium - Volume I, Number 3

Clipper 5.0 is almost here and with it comes a zillion or so new things to learn. Among them something called a "code block", but before we can discuss what code blocks do we need to agree on what code blocks are.

What code blocks are

Code blocks are a new data-type. This is important, they are not procedures, they are not functions, they are not objects, they are a new data-type and they act like data. They happen to contain compiled Clipper code. Nantucket describes them as "assignable unnamed functions".

Permit me a momentary digression

We accept the fact that characters are a data-type and we likely agree that a bunch of characters in a row (called a character string) is a data-type, perhaps as Clipper programmers we don't think of the two as particularly different.

There is no doubt that we consider numbers a data-type and in the Clipper/dBase world we are led to believe that dates and logical variables are unique data-types also.

One might suspect that defining data-types is something best left to Computer Science types or that there is some listing of acceptible data-types in a old binder somewhere and that everyone agrees that this is the sum total of them. Perhaps some people think that data-types are somehow tightly connected to the design of computers and that they need to be particularly discreet little chunks of stuff easily represented on the screen.

None of this could be further from the truth. Characters, numbers, dates and logicals were chosen because they represented things that we all find useful. Data-types could easily be rendered for telephone numbers and zipcodes though one could argue that they are just a patterned collection of numbers... but then so are dates.

Inventing a data-type requires only the definition of acceptible values and one or more operations that can be carried out on that data-type.

We can "add" numbers and we can "add" strings but the results are considerably different. We can "divide" two numbers but we cannot divide two strings. One the other hand we can convert a string to upper case but that operation is meaningless when applied to numbers.

Code blocks are essentially data, the data is executable code, but because they act like data it doesn't mean that you can automatically add or subtract them or convert them to upper case. Code blocks have their own set of operations appropriate to code blocks.

Operations on code blocks

Variables can be initialized to specific data-types and can be assigned values of that data-type.

   cVar := "this is a string"    // character string
   nVar := 5                     // numeric
   dVar := DATE()                // date
   lVar := .T.                   // logical
   aVar := { 1, 2, 3, 4 }        // array

It turns out that we can initialize and assign code blocks also.

   bVar := { || QOUT( "A Hello World Code Block" )}

Notice that a code block is contained within curly braces ({}) the same characters used to contain arrays. To distinguish them from arrays, code blocks require the inclusion of a set of two vertical bars (||). These bars also serve to delimit the formal parameters but more on that later.

We can "add" numbers, "concatenate" strings, "increment" dates, "invert" logicals, "sort" arrays and we can "execute" code blocks.

Using the proper terminology we can "evaluate" code blocks and the way we do it is through the EVAL() function or through a number of other functions that accept a code block as a parameter.

Notice that each data-type has some operation that is unique. This is quite probably the reason that we find any particular data-type worthwhile. If, for instance I could add the strings "123" + "456" and obtain an answer of "579", the need for a numeric data-type would be lessened.

Code blocks just like all other data-types can be passed as arguments to and can be returned from functions. In their ASCII representation they can saved to a file on the disk or stored in a .DBF field.

Evaluating code blocks

Let's EVAL() our first code block example.

   CLS
   bVar := { || QOUT( "A Hello World Code Block" )}
   EVAL( bVar )
   RETURN

Enter, compile, link and run this example and with any luck at all the phrase "A Hello World Code Block" appears on the screen. Never one to trust in luck... if the message did not appear on your screen check everything (you are using Clipper 5.0 right?) and try again.

You can simply EVAL() the code block directly by the way.

   CLS
   EVAL( { || QOUT( "A Hello World Code Block" ) })
   RETURN

Not to be overly confusing but someone has undoubtedly noticed that our simple example would continue to operate identically if we coded it more traditionally as follows:

   CLS
   QOUT( "A Hello World Code Block" )
   RETURN

The QOUT() function by the way is the equivalent of the ? command in function form (a quick look at STD.CH will confirm this for the doubting Thomas's among you).

Well if I can just use a function what's the point?

Hang on, not so fast... stick around, I'll show you more.

Don't get the idea that code blocks are equated with a method for printing values on the screen, they are not. While possible, (we proved that), that alone wouldn't serve much purpose.

A code block can just return a number

   CLS
   QOUT( EVAL( {|| 5 } ) )
   RETURN

A code block can perform math and return the answer

   CLS
   ? EVAL( {|| 5 * 5 } )
   RETURN

A code block can call another function

   CLS
   @ 10, 5 SAY EVAL( { || MyUdf() } )
   RETURN

   FUNCTION MyUdf
      RETURN "my udf return string"

Notice that in each case I have had to output the value returned by EVAL(). The EVAL() function works in that way just like any other function. EOF() for instance, returns the answer it doesn't automatically print it on the screen.

Like EOF(), RECNO() and all other Clipper functions you can assign the value returned by EVAL() to another variable.

   CLS
   cVar := EVAL( { || MyUdf() } )
   @ 10, 5 SAY cVar
   RETURN

   FUNCTION MyUdf
      RETURN "my udf return string"

We can return the return value of the EVAL() function

   CLS
   @ 10, 5 SAY MyUdf()
   RETURN

   FUNCTION MyUdf
      RETURN EVAL( { || "my udf return string" } )

And as was mentioned earlier, code blocks themselves can be returned

   CLS
   @ 10, 5 SAY EVAL( MyUdf() )
   RETURN

   FUNCTION MyUdf
      RETURN { || "my udf return string" }

It still looks pretty trivial

Well then let's add some parameters.

   CLS
   ? EVAL( { | nVar | nVar * nVar }, 6 )
   RETURN

In this case we have designed the block to accept a block parameter named nVar which is squared and returned. We have passed along the value 6 as the second argument in the EVAL() function. nVar will be assigned the value 6 and the answer 36 will be returned and displayed.

Talk of parameters and code block variables brings up an important question. What scope do the block parameters have? And the answer is that they have LOCAL scope which the following fragment should demonstrate.

   CLS
   nVar := 1
   ? EVAL( { | nVar | nVar := nVar * nVar }, 6 )
   ? nVar
   RETURN

The answers 36 and 1 are displayed which indicates that the assignment of 6 to nVar within the code block didn't affect the PRIVATE nVar which was assigned outside of the code block.

On the other hand, barring any naming conflicts variable references inside of the block take on the scope of the variables as defined outside of the block.

   CLS
   nVar := 6
   ? EVAL( { || nVar := nVar * nVar } )
   ? nVar
   RETURN

In this case nVar is displayed as 36 each time because the nVar within the block is the same variable as the one outside the block.

With some slightly convoluted code we can demonstrate another related and important plus about variable scope and code blocks.

   LOCAL nVar := 2
   CLS
   bSquare := { | nNum | nNum ^ nVar }
   ? MyUdf( bSquare )
   RETURN

   FUNCTION MyUdf( bBlock )
      RETURN EVAL( bBlock, 10 )

The answer displayed is 100 which might seem just a little bit odd because nVar was declared as LOCAL to the main routine but it was still visible in the MyUdf() function. Ordinarily that is behavior demonstrated by PRIVATE and PUBLIC scoped variables only.

It is referred to as exporting a variable and it happens because both nVar and the block referring to nVar are defined in the same routine. When the code block is passed as a parameter to another function nVar (in this example) is made visible to the function.

It gets a bit trickier

Multiple parameters can be passed along.

   CLS
   cRet := EVAL( { | dVar, cVar | QOUT( cVar, dVar ) },;
      DATE(), "today is : " )
   ? cRet
   RETURN

Compiling, linking and running this example will yield something a little unusual. The value "NIL" is output.

NIL is the return value of the QOUT() function (it says so in the documentation.) NIL, by the way, is another new data-type whose only value is NIL but that is a subject for another time. We don't have to display the return value of the EVAL() function of course but I thought that you might want to see it.

Multiple expressions can be included within a code block (but you cannot include commands (they aren't expressions)).

   CLS
   cRet := EVAL( { | nVar1, nVar2 | ++nVar1, --nVar2 }, 0, 0)
   ? cRet
   RETURN

Two numeric zero values were passed to the code block which assigned them to the formal parameters nVar1 and nVar2. The first expression incremented nVar1, the second expression decremented nVar2, (the comma is used to separate expressions). When there are multiple expressions in a code block the "value" of the code block is the value of the last expression and in this example, cRet was assigned the value of nVar2.

And don't be overly discouraged by the "no commands" restriction, because we can include functions we can easily write a UDF that executes as many commands as we like.

   EVAL( { || MyUdf() } )
   RETURN

   FUNCTION MyUdf
      CLS
      @ 10, 5 SAY "This is a test"
      RETURN NIL

Gets rid of a few macros

Code blocks themselves can be passed as parameters which is arguably their strongest point. The closest we've been able to come to this kind of functionality in Summer '87 is through the use of macros to express a function name.

   DO CASE
   CASE nKey == "A"
      cFunc := "addrec()"
   CASE nKey == "D"
      cFunc := "delrec()"
   ENDCASE

   &cFunc.

The unnecessary use of macros is always best avoided and using blocks would be ideal here.

   DO CASE
   CASE nKey == "A"
      bFunc := { || addrec() }
   CASE nKey == "D"
      bFunc := { || delrec() }
   ENDCASE

   EVAL( bFunc )

They're used within Clipper

It isn't a poor guess to suppose that we have access to code blocks because the developers of 5.0 needed them. They are used extensively in 5.0 and can be seen being referenced in the STD.CH file.

The following list of commands reference code blocks :

   SET FORMAT TO
   @ SAY/GET RANGE
   MENU TO
   SET KEY TO
   COUNT
   SUM
   AVERAGE
   DELETE FOR/WHILE
   RECALL FOR/WHILE
   REPLACE WITH FOR/WHILE

Additionally some Clipper functions have been enhanced to allow code blocks as arguments.

   ASCAN()
   ASORT()
   SETKEY()       // this one is new
   VALTYPE()      // this one is new also

While most of the commands have hidden the internal workings of the code block a few of them use documented "iterator" functions.

Iterator functions

AEVAL() and DBEVAL() are iterative functions. Essentially what they do is EVAL() a code block against each element of an array (AEVAL()) or each record of a .DBF file (DBEVAL()).

To use the AEVAL() function you must pass at least two arguments, the first is the array name and the second is the block to execute.

   aVar := { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   bDouble := { | nVal | QOUT( TRANSFORM( ( nVal * 2 ), "@B 999")) }
   AEVAL( aVar, bDouble )

This example displays the value of each element after it has been doubled. I tossed in the TRANSFORM() function to "add some spice".

Optionally third and/or fourth arguments can be passed to AEVAL() that restrict the range of elements to be processed. The third argument is the index into the array representing the first element to apply the block to (if you don't pass it the default is 1).

The fourth argument is the number of elements to process, (not the last element, the number of elements). If you don't pass this along the default is all the elements in the array.

   aVar := { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   bDouble := { | nVal | QOUT( TRANSFORM( ( nVal * 2 ), "@B 999")) }
   AEVAL( aVar, bDouble, 5, 3)

This example restricts the elements to be "blocked" to the 5th, 6th and 7th elements.

Lets have a "real" example shall we?

   // CDIR.PRG

   // Some minor work remains to make it operate "properly" like
   // it could display the file count and disk space free and you
   // could add other DOS directory options /O (sort) for instance
   // using the AEVAL() function.

   // When I wrote this routine Nantucket had a different definition
   // for "<cDirSpec>" (a directory specification) than Microsoft and
   // I added the DirAdj()function to tweak the Nantucket definition.

   // Testing with 5.2 reveals the "difference" still exists.

   // compile with : clipper cdir /m/n/w

   #include "directry.ch"


   FUNCTION Main( cParm1, cParm2 )
      LOCAL cPath, lPause

      DO CASE

      CASE ( PCOUNT() == 0 )
         cPath := "*.*"
         lPause := .F.

      CASE ( PCOUNT() == 1 )
         IF ( UPPER( cParm1 ) == "/P" )
            cPath := "*.*"
            lPause := .T.

         ELSE
            cPath := DirAdj( cParm1 )
           lPause := .F.

         ENDIF

      CASE ( PCOUNT() == 2 )
         cPath := DirAdj( cParm1 )
         lPause := ( UPPER( cParm2 ) == "/P" )

      ENDCASE

      QOUT()
      AEVAL( DIRECTORY( cPath, "D" ), ;
         { | aFile | DirSay( aFile, lPause ) } )

      RETURN NIL


   FUNCTION DirAdj( cPath )
      LOCAL cRet := LTRIM( RTRIM( cPath ))

      IF ( RIGHT( cPath, 1 ) == "\" )
         cRet := cPath + "*.*"

      ELSEIF ( RIGHT( cPath, 1 ) == ":" )
         cRet := cPath + "\*.*"

      ELSEIF ( RIGHT( cPath, 1 ) == "." )
         cRet := cPath + "\*.*"

      ENDIF

      RETURN cRet


   // list the files

   FUNCTION DirSay( aFiles, lPause )
      STATIC nLines := 0

      IF ( nLines >= 23 .AND. lPause )
         QOUT( "Press any key to continue . . . " )
         INKEY(0)
         nLines := 0
      ENDIF

      nLines++

      QOUT( DirName( aFiles[ F_NAME ] ), " ", ;
         DirSize( aFiles[ F_SIZE ], aFiles[ F_ATTR ] ), ;
         DirDate( aFiles[ F_DATE ] ), " ", ;
         DirTime( aFiles[ F_TIME ] ) )

      RETURN NIL


   // dump the dot

   FUNCTION DirName( cFile )
      LOCAL cRet, cTmp, nDot, cExt

      IF ( LEFT( cFile, 1 ) == "." )
         cRet := LEFT( cFile + SPACE( 12 ), 12 )

      ELSE
         cTmp := cFile + "  ."
         nDot := AT( ".", cTmp )
         cExt := LEFT( SUBS( cTmp, nDot + 1 ) + SPACE( 3 ), 3 )
         cRet := LEFT( SUBS( cTmp, 1, nDot - 1 ) + SPACE( 9 ), 9 ) + cExt

      ENDIF

      RETURN cRet


   // could be a directory

   FUNCTION DirSize( nSize, cAttr )
      RETURN IF( ( "D" $ cAttr ), "&ltDIR>", STR( nSize, 8 ))


   // date delimiters aren't DOS-like

   FUNCTION DirDate( dDate )
      RETURN STRTRAN( DTOC( dDate ), "/", "-")


   // convert to 12 hour format

   FUNCTION DirTime( cTime )
      LOCAL cRet, nHr := VAL( LEFT( cTime, 2 ))

      IF ( nHr < 12 )
         cRet := STR( nHr ;
            + IF(( nHr ==  0 ), 12,  0 ), 2 ) + SUBS( cTime, 3, 3 ) + "a"

      ELSE
         cRet := STR( nHr ;
            - IF(( nHr == 12 ),  0, 12 ), 2 ) + SUBS( cTime, 3, 3 ) + "p"

      ENDIF

      RETURN cRet

Compile and link this example and you have a DOS directory program.

To use the DBEVAL() function you must have a .DBF file open in the currently selected work area and you must pass at least one argument which is the block to execute against each record.

   USE test

   // test.dbf must have the following structure
   // and should have some data in it

   // NAME   C  30  0
   // CITY   C  20  0
   // STATE  C   2  0

   bShow := { || QOUT( field->name, field->city, field->state ) }
   DBEVAL( bShow )

   CLOSE test

Optionally there are five other arguments corresponding to

I will have to avoid giving any explicit details about the iterator functions like AEVAL() and DBEVAL() in this article. They honestly deserve a separate article with more substantial examples.

And that isn't all

Except for the example which demonstrated eliminating the macroing of functions it is likely that you cannot think of one overwhelming reason to use a code block instead of a function call. Don't let this bother you. Code blocks are not intended to replace functions they are not the "greatest thing on Earth", they are what they are they do what they do and their best use is where they are needed and not where they can be molded to fit.

We will certainly see their use enhanced in future versions of Clipper I think the ACHOICE(), DBEDIT() and MEMOEDIT() functions are likely candidates but well before then I'm certain that we will see numerous good (and probably some not so good) examples published.

Please don't rush headlong into using code blocks but also don't back away... just take them in stride like all the other things that make Clipper "Clipper".

About the author:

Tom Leylan has been a guest speaker at conferences around the world including events in Amsterdam, London, Sydney, Johannesburg, Miami Beach, Orlando, Los Angeles, Palm Desert and Honolulu. He has written numerous articles and is the author of the book, Writing Applications with Clipper © 1994 MIS Press ISBN: 1-55851-382-5.

Tom can be reached at: postmaster@leylan.com

© 1990, The Leylan Factor (All Rights Reserved)