Monday, January 26, 2009

Codeblocks - by Tom Leylan - 30 June 1996

This article on code blocks is publish here with the permission of Tom Leylan (he has moved to New York) on 30 June 1996.

It is rather old (I wrote it for The Aquarium prior to the 5.0 release) but somewhat applicative today. It may be hard to find but look fora copy of my book. It shows how to write Clipper applications and demonstrates a method which emphasizes writing the least amount of code to obtain the most flexible, reusable solutions.

They're mysterious they're fun they're often misunderstood they're code blocks

by Tom Leylan

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 datatype. This is important, they are not procedures, they are not functions, they are not objects, they are a new datatype 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 datatype and we likely agree that a bunch of characters in a row (called a character string) is a datatype, perhaps as Clipper programmers we don't think of the two as particularly different.

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

One might suspect that defining datatypes is something best left to Computer Science types or that there is some listing of acceptible datatypes in a old binder somewhere and that everyone agrees that this is the sum total of them. Perhaps some people think that datatypes 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. Datatypes 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 datatype requires only the definition of acceptible values and one or more operations that can be carried out on that datatype.

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 datatypes and can be assigned values of that datatype.

sVar := "this is a string" // string

nVar := 5 // numeric

dVar := date() // date

tVar := .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 datatype has some operation that is unique. This is quite probably the reason that we find any particular datatype worthwhile. If, for instance I could add the strings "123" + "456" and obtain an answer of "579", the need for a numeric datatype would be lessened.

Code blocks just like all other datatypes 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 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

sVar := eval( {|| MyUdf() } )

@ 10, 5 say sVar

return

function MyUdf

return "my udf return string"

We can instead 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 (actually 36.000000000) 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

cls

nVar := 2

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

sRet := eval( {| dVar, sVar | Qout(sVar, dVar) }, date(), "today is :")

? sRet

return

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

NIL is the return value of the Qout() function (it says so in the documentation). NIL, by the way, is another new datatype 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

sRet := eval( {| nVar1, nVar2 | ++nVar1, --nVar2 }, 0, 0)

? sRet

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, sRet 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.

cls

sRet := eval( {|| MyUdf() } )

return

function MyUdf

cls

@ 10, 5 say "This is a test"

return 0

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 iKey == "A"

sFunc := "addrec()"

case iKey == "D"

sFunc := "delrec()"

endcase

&sFunc.

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

do case

case iKey == "A"

bFunc := {|| addrec() }

case iKey == "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 := {| iVal | Qout( transform( (iVal * 2), "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" and to eliminate those trailing zeros.

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 := {| iVal | Qout( transform( (iVal * 2), "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

From an idea presented by Bill Christison on NANFORUM and rather heavily modified by Tom Leylan

Some work remains to make it operate "properly" but when I wrote this there was a bug in the directory() function which made my DirAdj() function necessary

compile with : Clipper cdir /M /N /W */

#define F_NAME 1

#define F_SIZE 2

#define F_DATE 3

#define F_TIME 4

#define F_ATT 5

FUNCTION main(sParm1, sParm2)

local sPath, tPause

do case

case pcount() == 0

sPath := "*.*"

tPause := .f.

case pcount() == 1

if (upper(sParm1) == "/P")

sPath := "*.*"

tPause := .t.

else

sPath := DirAdj(sParm1)

tPause := .f.

endif

case pcount() == 2

sPath := DirAdj(sParm1)

tPause := (upper(sParm2) == "/P")

endcase

Qout()

aeval( directory(sPath, "d"), {|aFile| DirSay(aFile, tPause)} )

return NIL

/* this is incomplete but should not be necessary */

FUNCTION DirAdj(sParm1)

local sPath := ltrim(rtrim(sParm1))

if right(sParm1, 1) == "\"

sPath := sParm1 + "*.*"

elseif right(sParm1, 1) == ":"

sPath := sParm1 + "\*.*"

elseif right(sParm1, 1) == "."

sPath := sParm1 + "\*.*"

endif

return sPath

/* list the files */

FUNCTION DirSay(aFiles, tPause)

static nlines := 0

if nlines >= 23 .and. tPause

QOut( "Strike a key when ready . . . " )

inkey(0)

nlines := 0

endif

nlines++

QOut( DirFile(aFiles[F_NAME]), " ", ;

DirSize(aFiles[F_SIZE], aFiles[F_ATT]), ;

DirDate(aFiles[F_DATE]), " ", ;

DirTime(aFiles[F_TIME]) )

return NIL

/* dump the dot */

FUNCTION DirFile(sFile)

local sTemp, iDot, sName, sExt

if left(sFile, 1) == "."

sName := left(sFile + space(12), 12)

else

sTemp := sFile + " ."

iDot := at(".", sTemp)

sExt := left(subs(sTemp, iDot + 1) + space(3), 3)

sName := left(subs(sTemp, 1, iDot - 1) + space(9), 9) + sExt

endif

return sName

/* could be a directory */

FUNCTION DirSize(iSize, sAtt)

return if( ("D" $ sAtt), "

", str(iSize, 8))

/* date delimiters are screwy */

FUNCTION DirDate(dDate)

local sDate := strtran(dtoc(dDate), "/", "-")

return str(val(left(sDate, 2)), 2) + right(sDate, 6)

/* convert to 12 hour format */

FUNCTION DirTime(sTime)

local iHr, sRet

iHr := val(left(sTime, 2))

if (iHr <>

sRet := str(iHr + if((iHr == 0), 12, 0), 2) + subs(sTime, 3, 3) + "a"

else

sRet := str(iHr - if((iHr == 12), 0, 12), 2) + subs(sTime, 3, 3) + "p"

endif

return sRet

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 you should have some data in it

NAME C 30 0

CITY C 20 0

STATE C 2 0

*/

bShow := {|| Qout( NAME, CITY, STATE ) }

dbeval( bShow )

close test

Optionally there are five other arguments corresponding to

a FOR condition

a WHILE condition

a NEXT n records specifier

a RECORD n specifying a single record number

a REST logical value used to indicate whether the record pointer should be rewound prior to beginning the process or whether to process only the remaining records

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 (c) 1994 MIS Press ISBN: 1-55851-382-5.

Tom can be reached electronically at: tleylan@nyiq.net

No comments:

Post a Comment

Welcome to Clipper... Clipper... Clipper


In 1997, then using Delphi 3, I had already created 32-bits Windows applications for HRIS, ERP and CRM. In 2007, using Ruby on Rails, an AJAX powered CRM site running on Apache & MySQL was created and I am now using Visual Studio .Net 2008 to create web-based projects and Delphi 7 for Win32 applications using SQL2005 & DBFCDX.

So, why then am I reviving the Original Clipper... Clipper... Clipper via a Blog as CA-Clipper is a programming language for the DOS world ? Believe it or not, there are still some clients using my mission-critical CA-Clipper applications for DOS installed in the late 80's and up to the mid 90's. This is testimony to CA-Clipper's robustness as a language :-)

With the widespread introduction of Windows 7 64-bits as the standard O/S for new Windows based PCs & Notebooks, CA-Clipper EXE simply will not work and it has become imperative for Clipper programmers to migrate immediately to Harbour to build 32/64 bits EXEs

Since 28th January 2009, this blog has been read by 134,389 (10/3/11 - 39,277) unique visitors (of which 45,151 (10/3/11 - 13,929) are returning visitors) from 103 countries and 1,574 cities & towns in Europe (37; 764 cities), North America (3; 373 cities) , Central America & Caribeans (6; 13 cities), South America(10; 226 cities), Africa & Middle-East (12; 44 cities) , Asia-Pacific (21; 175 cities). So, obviously Clipper is Alive & Well : -)


TIA & Enjoy ! (10th October 2012, 11:05; 13th November 2015)


Original Welcome Page for Clipper... Clipper... Clipper

This is the original Welcome Page for Clipper... Clipper... Clipper, which I am republishing for historical and sentimental reasons. The only changes that I have made was to fix all the broken links. BTW, the counter from counter.digits.com is still working :-)

Welcome to Chee Chong Hwa's Malaysian WWW web site which is dedicated to Clipperheads throughout the world.

This site started out as a teeny-weeny section of Who the heck is Chee Chong Hwa ? and has graduated into a full blown web site of more than 140 pages (actually hundreds of A4 size pages) ! This is due to its growing popularity and tremendous encouragements from visiting Clipperheads from 100 countries worldwide, from North America, Central America, Caribbean, South America, Europe, Middle-East, Africa and Asia-Pacific. Thanx Clipperheads, you all made this happen !


What is Clipper ?

You may ask, what is this Clipper stuff ? Could Clipper be something to do with sailing as it is the name of a very fast sailing American ship in the 19th century ?

Well, Clipper or to be precise, CA-Clipper is the premier PC-Software development tool for DOS. It was first developed by Nantucket Corporation initially as a compiler for dBase3+ programs. Since then, CA-Clipper has evolved away from its x-base roots with the introduction of lexical scoping & pre-defined objects like TBrowse. As at today, the most stable version ofClipper is 5.2e while the latest version, 5.3a was introduced on 21 May 1996.

As at 11th November, 1996, an unofficial 5.3a fixes file was made available by Jo French. See the About CA-Clipper 5.3a section for more details. BTW, Jo French uploaded the revised 5.3a fixes file on 20th November, 1996.

Latest News

The latest news is that CA has finally released the long-awaited 5.3b patch on 21 May, 1997.

For 5.3b users, you must a take a look at Jo French's comments on unfixed bugs in 5.3b.

BTW, have you used Click ? If you're a serious Clipperprogrammer and need an excellent code formatter, Click is a natural choice. How to get it ? Simple, access Phil Barnett's site via my Cool Clipper Sites.

32-bits Clipper for Windows ?

Have you tried Xbase ++ ? Well, I have and compared to Delphi (my current Windows programming tool of choice), I'm still sticking to Delphi.

Anyway, you should visit the Alaska Home Page. Give it a chance and then draw your own conclusions !.

The Harbour Project

Is this the future of Xbase ? Take a look at at the Harbour Project

You are Visitor # ...

According to counter.digits.com, you are visitor since 3 June 1996.

If you like or dislike what you see on this website, please drop me a line by clicking the email button at the bottom of this page or better still, by filling out the form in my guest book. If you are not sure what to write,click here to take a look at what other Clipperheads have to say.