0% found this document useful (0 votes)
45 views

Code Blocks, Inside and Out: Data Based Advisor

1. The document discusses advanced techniques for using code blocks in Clipper programming, including assigning variables inside code blocks, passing parameters by value and reference, creating get/set blocks, evaluating macros inside code blocks, creating temporary variables, exporting local variables through code blocks, and returning code blocks from functions. 2. Key points covered include using the ":=" operator to assign variables inside code blocks, passing parameters by reference by prepending "@", using get/set blocks with built-in functions, evaluating macros early or late by enclosing in parentheses, creating temporary variables by declaring as parameters, and exporting local variables through code blocks. 3. Code blocks allow saving program code as parameters to reduce code and make programs more

Uploaded by

Jose Cordero
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
45 views

Code Blocks, Inside and Out: Data Based Advisor

1. The document discusses advanced techniques for using code blocks in Clipper programming, including assigning variables inside code blocks, passing parameters by value and reference, creating get/set blocks, evaluating macros inside code blocks, creating temporary variables, exporting local variables through code blocks, and returning code blocks from functions. 2. Key points covered include using the ":=" operator to assign variables inside code blocks, passing parameters by reference by prepending "@", using get/set blocks with built-in functions, evaluating macros early or late by enclosing in parentheses, creating temporary variables by declaring as parameters, and exporting local variables through code blocks. 3. Code blocks allow saving program code as parameters to reduce code and make programs more

Uploaded by

Jose Cordero
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

Code blocks, inside and out

Data Based Advisor


December 01, 1991 | Spence, Rick

When I showed you code blocks last month, I started simple, showing you how to pass program code as a
parameter, not only reducing the amount of code you write, but making programs more compact.

This month, I'll discuss some advanced issues concerning code blocks, techniques you can use to streamline
your programs. I'll cover the following topics:

1. Assigning to variables from inside code blocks


2. Passing parameters to code blocks by value and by reference
3. Get/set blocks
4. Early and late evaluation of macros inside code blocks
5. Creating temporary variables inside code blocks
6. Exporting local variables through a code block
7. Exporting static functions through a code block
8. Returning code blocks from functions
9. Accessing LOCAL variables in code blocks compiled at runtime.

Assigning to variables

As I've said many times before, Clipper's "=" operator is overloaded. Stated simply, it acts differently
depending on how you use it. For example, if you use it in a statement, it performs an assignment operation:

i = 1 // Assign 1 to the variable I

If you use it inside an expression, however, as in:

IF i = 1

and

DO WHILE i = 1

it performs a comparison.

Code blocks contain expression, therefore if you use the = operator inside a code block, it performs a
comparison. For example:

i = 1
b = { || i = 3 }
? eval(b) // .F. - it compares i to 3 here
? i // 1

To assign to a variable inside a code block, you must use the inline assignment operator, ":=", as:

i = 1
b = { || i := 3 }
? eval(b) // 3
? i // 3

Unlike the = operator, the inline assignment operator isn't overloaded; it always performs an assignment, so
you can also use it in a statement. The following code example, the preferred way of doing things, is identical
to the previous example:

i := 1
b := { || i := 3 }
? eval(b) // 3
? i // 3

Convention: Always use the inline operator, :=, to perform assignments, and use the = operator only for
comparisons.
Passing parameters by value and reference

Consider the following code fragment:

i := 1
b := { | n | n := 3 }
? eval( b, i ) // 3
? i // 1 - i is unchanged

As you can see, I passed the variable "i" to code block b. Code block b receives it in the formal parameter n,
then assigns 3 to it. The eval() function returns the result of the last expression it evaluated, in this case 3.
However, when you ask the value of i, it's unchanged. From this you can gather that by default, parameters are
passed to code blocks by value.

This is the same as when you pass a parameter to a function, but when you call a function, you can prefix the
parameter's name with the @ character to force Clipper to pass it by reference. You can do exactly the same
with a code block:

i := 1
b := { | n | n := 3 }
? eval (b, @i ) // 3
? i // 3

Note: You can pass parameters to code blocks by reference by preceding the name with the @ character.

Get/set blocks

Get/set blocks are specially formatted code blocks used by several built-in functions and get objects. The
following is a get/set block for the lname field:

FIELD lname
bExample := { | p | iif( p == NIL, lname, lname := p }

The code block does one of two jobs, depending on whether you pass it a parameter. If you don't, it returns
the current value of lname. If you do, it updates lname to whatever you pass:

? eval (bExample, "Spence") // Spence


? lname //Spence
lname := "Brown"
? eval (bExample) // Brown
? lname // Brown

Two built-in functions, fieldblock() and fieldwblock(), return get/set blocks for fields. This allows you to write
code to process any database without using the (slow) macro operator. For example, you can replace:

cFname := "lname"
. .
cVar := &cFname
@ 10, 10 GET cVar
READ

IF updated()
REPLACE &cFname WITH cVar

with:

cFname := "lname"
bGetFld := fieldblock(cFname)
cGetVar := eval (bGetFld)
@ 10, 10 GET cGetVar
READ

IF updated()
eval (bGetFld, bGetVar)
ENDIF

If you're prepared to edit the field directly, you can use get objects to shorten the code:

cFname := "lname"
readmodal ( { getnew (10, 10, ; fieldblock (cFname)," ") } )
It isn't terribly readable, but it is concise! Readmodal() is the function that implements the standard READ
command. It expects an array of get objects as a parameter, which you create on-the-fly and fill with one
element. The element is the return result of getnew(), the get constructor function. This allows you to perform
a get on a field or variable using a get/set block, which you create with a call to fieldblock().

Macro evaluation

Clipper has two ways of dealing with macro expansion inside code blocks. The first way is to expand the
macro when the block is assigned:

cFilt := "lname = 'Spence'"


bExample := {|| &cFiltt }

Here the macro operator generates code to compare lname with the string constant "Spence," and saves it
inside the code block. For example:

lname := 'Jones'
fname := 'Rick'
cFilt := "lname = 'Spence'"
bExample := {|| &cFilt }

? eval (bExample) // .F.


cFilt := "fname = 'Jack'"
lname := 'Spence'
? eval (bExample) // .T. , we still compare lname to Spence here

The last call to eval() is the most important. Even though two lines above it changed the contents of cFilt,
the code block still contains code to compare lname to "Spence". This is called early evaluation—the code is
generated once, when you assign the block to a variable.

The alternate method of dealing with macros inside code blocks is called late evaluation. With this method,
every time a code block is evaluated, eval() re-evaluates the macro and regenerates the code. You indicate
late evaluation by enclosing the expression to be macro-expanded inside parentheses:

lname := 'Jones'
fname := 'Rick'
cFilt := "lname = 'Spence'"
bExample := {|| &(cFilt) }
? eval(bExample) // .F.
cFilt := "fname = 'Jack'"
lname := 'Spence'
? eval(bExample) // .F. , We're comparing fname to Jack here

Late evluation is wasteful if what you're macro-expanding doesn't change.

Rule: Clipper's default method of handling macro expansion inside a code block is to generate the code once
when the assignment takes place. If you enclose the macro expression in parentheses, Clipper regenerates
the code every time you evaluate the block.

Creating temporary variables

You can use many expressions inside code blocks. However, how do you save the results of one expression so
you can use it later in another? One solution is to assign it to a variable external to the code block, as in:

LOCAL temp, bExample


bExample := { || temp := a(), b(), c(temp) }

However, since temp is only used inside the block, and isn't used anywhere inside the function in which it's
declared, a better solution is to declare bExample as local to the code block. You can't use a LOCAL declaration
inside a code block but you can declare it as a formal parameter:

LOCAL bExample := {|temp| temp := a(), b(), c(temp) }

You don't pass a parameter to bExample when you evaluate it, but Clipper doesn't care.

Note: You can create local variables inside code blocks by declaring them as parameters.

A good example is setting a tbrowse object's skipBlock instance variable when browsing an array. The skipBlock
instance variable is a code block, and it's used to skip in the data source of whatever you're browsing. If
you're browsing databases, for example, skipBlock contains a code block to call a function that performs a
SKIP command. If you're browsing arrays, you can set skipBlock to a function that modifies the array index.
When the tbrowse methods evaluate skipBlock, they pass it a parameter, telling it how many row to move,
either forward, or backwards. SkipBlock must return how many elements it actually skipped, which can be
different from the number it was told to skip.

Consider the case of browsing arrays. SkipBlock is told to skip so many elements. Assume the array index is
called i. SkipBlock then must try and change i by the requested number, but must ensure i isn't made zero or a
negative, or that it exceeds the length of the array. The code block must return the actual number of element
it skipped. The following code block does this:

// assume we're browsing the array ar


// assume the array index is i
// assume the tbrowse object is oTbr

oTbr:skipBlock := { | n, nSavei | ;
nSavei := i, ;
i := iif( n > 0, ;
min (len(ar), i + n), ;
max(1, i + n)), ;
i - nSavei }

It goes through three distinct steps:

1. Saves the original value of i


2. Modifies i according to n, but ensures i doesn't go out of bounds
3. Returns the number of elements it skipped

The number of elements skipped is where you are now, minus were you started. Where you started is saved in
the temporary variable nSavei, which is local to the code block.

Exporting local variables

As you know, local variables are only visible in the routine in which you declare them; they aren't visible in
routines called from the declaring routine unless you pass them as parameters. However, there is an exception
to this rule when you use code blocks. Consider the following code sample:

FUNCTION test
LOCAL bExample, i
i := 1
bExample := {|| i}
test1 (bExample)
...

FUNCTION test1 (bFormal)


? eval (bFormal) // 1 . .

This is the case even if the test1() function declares its own variable i. The code block refers to the variable
that was in scope at compile-time, in this case the variable i inside the test function. Remember, the compiler
generates the code for the code block, and it generates code to refer to the variables in scope of that time.
Local variables are visible in called routines if they're accessed through a code block. This feature greatly
extends the use of code blocks, since it means you can pass a parameter to a routine that will operate on your
variables.

Exporting static functions

A similar argument applies to calling static functions from inside a code block, as in:

PRG1.PRG
STATIC FUNCTION t1
RETURN 3

FUNCTION t2
LOCAL b := { || t1 () }
? eval (b) // 3
t3 (b)
RETURN NIL

PRG2.PRG
FUNCTION t3(b)
? eval (b) // 3
RETURN NIL

The t2 function creates a code block that calls the static t1 function. Since the functions reside in the same
program file, this is valid. Remember, the compiler generates code for the code block,a nd when it compiles it,
the t1 function is in scope. t2 then evaluates the block, and the eval() function returns 3. Now comes the
interesting part. The t2 function calls t3, a function in a different program file. It passes it the code block to
call the static function. If t3 had tried to call t1 directly, the linker would have reported an error; t1 is only
visible from inside PRG1. In this case, however, the code block is passed which gives access to an otherwise
inaccessible routine.

Returning code blocks from functions

This example is the most difficult to understand, but probably the most powerful. Consider the following:

FUNCTION t
LOCAL b
b:= t1(1)
? eval (b) // 1
RETURN NIL

FUNCTION t1
LOCAL i := 1
RETURN {|| i}

The t1 function returns a code block that refers to a local variable. As soon as t1 returns, its local variables are
disposed of, so what variable does the block refer to? The calling routine evaluates the block which contains
the number 1. Clipper holds on to a local variable as long as there's a code block that refers to it. As soon as
the code block goes out of scope (in this case when the t function returns), the local variable is released.

But things can get tricky:

FUNCTION t
LOCAL b1, b2
b1 := t1()
b2 := t1()
? eval (b1) // 1
? eval (b1, 3) // 3
? eval (b1) // 3
? eval (b2) // 1
RETURN NIL

// Return a get / set block for the local variable i

FUNCTION t1
LOCAL i
RETURN { |p| iif(p == NIL, i, i :=p) }

Function t1 now returns a get/set block for the local variable i. The t function calls it twice. From the output
from the t function, notice that Clipper has kept two distinct copies of t1's local variable i. So, every time you
call t1, Clipper creates a new copy of its local variable referred to in the code block it returns. That variable is
maintained until the block that refers to it goes out of scope.

Here's an extremely important example of this concerning tbColumn objects. Assume you want to write a
routine to browse a two-dimensional array using tbrowse and tbColumn objects. Your routine is passed the
array, and it needs to create a tbColumn object for each column of the array. For example, if the array is
declared with:

LOCAL aExample [10, 4]

and you use the ubiquitous i as the array index, you must create tbColumn objects with the following
block instance variables:

{|| aExample[i, 1] }
{|| aExample[i, 2] }
{|| aExample[i, 3] }
{|| aExample[i, 4] }

Your routine must be generic. It doesn't know in advance how many columns it must create; it must do it
dynamically. Your first attempt at writing this code would probably look like this:

FUNCTION browse array(ar)


LOCAL oTbc, oTbr, nCols, i, nColNum
oTbr := tbrowseNew(.....)
nCols := len (ar[1])

// for each column in the array

FOR nColNum := 1 TO nCols


oTbc := tbColumnNew("...", {|| ar[i, nColNum] } )

oTbr:addColumn(oTbc)

NEXT
..

Do you see the problem? The code block says "return the value of ar[i, nColNum]." Whenever you evaluate
the block, it gets the current value of i and the current value of nColNum, and uses these as array indices. You
want it to do this for i, since you'll modify i as the user moves up and down in the array.

However, you don't want it to get the value of nColNum every time. You must "hard-code" the value of
nColNum in the array somehow. The first code block must contain {|| ar[i, 1]}, etc.

The solution is to call a function that returns the code block, then passes nColNum as a parameter to the
function. Since the function returns a code block that refers to a local variable, Clipper creates and maintains a
new variable eact time you call the function:

STATIC i, ar
FUNCTION browse array(aBrowse)
LOCAL oTbc, oTbr, nCols, nColNum
ar := aBrowse oTbr := tbrowseNew(.....)
nCols := len(ar[1])
// for each column in the array
FOR nColNum := 1 TO nCols
oTbc := tbColumnNew("...", ablock(nColNum))
oTbr:addColumn(oTbc)
NEXT . .

FUNCTION ablock(nColNum)
RETURN {|| ar[i, nColNum] }

Why don't you pass the array or the variable I ? You must only have one copy of i, since you'll modify it in the
skipBlock, goTopBlock, and goBottomBlock instance variables. Ablock must be able to see i, however, so you
should make it an external static.

You could pass the array, then Clipper would create and maintain separate pointers to it. It would work, since
the pointers would point to the same place, but it's wasteful. Therefore, declare it as an external static so it's
visible inside ablock.

Accessing LOCAL variables created at runtime

You can create code blocks at runtime using the macro operator. However, you cannot access LOCAL or
STATIC variables from inside a macro expansion, which means code blocks created at runtime cannot refer to
LOCAL or STATIC variables. The following fails, for example:

LOCAL nVar, bExample, cExpr


nVar := 1 cExpr := "{|| nVar}"
bExample := &cExpr
? eval(bExample) // Runtime error

The solution is to create the block using a parameter, and pass the LOCAL or STATIC variable as a parameter
to eval(), with:

LOCAL nVar, bExample, cExpr


nVar := 1
cExpr := "{|p| p}"
bExample := &cExpr
? eval (bExample, Nvar) // 1

Summary

In this article I showed some advanced ways to use code blocks, and defined how Clipper works in some
extremes cases. I showed you how you can use code blocks to export LOCAL variables and static functions,
and how to create LOCAL variables inside the block. I also showed you how Clipper maintains LOCAL variables
that would otherwise go out of scope when accessed by a code block returned from a function.

A former member of the Nantucket development team, Rick Spence owns Software Design
Consultants, a training and consulting company. He's also the author of Clipper Programming Guide,
2nd Edition, published by Data Based Solutions, Inc. and Slawson Communications, and technical
editor of Compass for Clipper, a monthly journal from Island Publishing. You can contact Rick through
Software Design Consultants at (011) (44) 452-812733, on MCI (LSPENCE), or on CompuServe
(71760, 632).

You might also like