Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
65 views

Code Block Basics: February 01, 1991 - Spence, Rick

The document discusses code blocks in Clipper 5.0, which allow storing compiled program code in variables. Code blocks are a new data type that contain expressions rather than data. They can be passed to the EVAL() function to execute the stored code. Using code blocks avoids repeatedly compiling the same expression at runtime and allows delaying code execution. Code blocks can also accept parameters to parameterize stored code.

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)
65 views

Code Block Basics: February 01, 1991 - Spence, Rick

The document discusses code blocks in Clipper 5.0, which allow storing compiled program code in variables. Code blocks are a new data type that contain expressions rather than data. They can be passed to the EVAL() function to execute the stored code. Using code blocks avoids repeatedly compiling the same expression at runtime and allows delaying code execution. Code blocks can also accept parameters to parameterize stored code.

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 block basics

Data Based Advisor


February 01, 1991 | Spence, Rick

Don't be intimidated by code blocks. This article will teach you what they are and how to use them, and how to
make your code more efficient with a couple of code block tricks.

While code blocks are one of Clipper 5.0's most important features, they seem to be difficult for many
programmers to grasp. This article explains what they are, and how and when you use them. First, we'll review
Summer 87's data types, then discuss code blocks as a new type. I'll compare code block usage with macros,
showing how you can replace a repetitive macro expansion with a code block to make code more efficient.

After covering the basics, I'll show you some advanced techniques, like how to process an entire array without
using a loop and how to calculate many values with one pass through a range of database records. Finally,
you'll learn how to store a code block in a database, and create one while the program is running.

Summer'87 data types

Clipper Summer'87 supports the following data types:

* Array
* Character
* Date
* Logical
* Numeric

You create variables by assigning to them. A variable contains data of one of the preceding types. A variable
can change type during program execution, but at any time, a variable has only one type.

You create an array variable by declaring an array with either the PRIVATE or PUBLIC statement. To create
variables of other types, you assign them either an expression or a constant of the appropriate type.

You write constants using a predefined sequence of characters. A logical constant is the letter T or F surrounded
by periods (.T.); a character constant is a sequence of characters enclosed in quotes ("Negative Balance"); and
a numeric constant is a sequence of digits with an optional decimal point (1.98).

Clipper doesn't support date constants; you create a date by passing a character expression, formatted as a
date, to the CTOD() function, as in:

today = CTOD("01/01/91")

Once you've created variables, you'll want to do something with them. What you do, of course, depends on the
type. Built-in functions and commands expect arguments of a particular type. The SUBSTR() function, for
example, expects as parameters a character expression and two numeric expressions.

Certain operators require operands of a specific type. The multiply operator, for example, expects two numeric
operands. Other operators can be overloaded. This doesn't mean they eat too much; rather, it means you can
use the operator with different types of operands. The plus operator, for instance, adds dates or numbers, or
concatenates character strings.

Code blocks are a new type

Code blocks (blocks for short) are nothing more than a new data type. You store them in variables, pass them
as parameters, uses them as function return results, copy them, etc. They have a type, just like other
variables. The TYPE() and VALTYPE() functions return "B" when passed a block variable.

There are no operators defined to work with blocks; you can't add or subtract them. There are, however,
several new functions that expect blocks as parameters, and other Summer'87 functions that have been
modified to accept block parameters--as you'll see shortly.

You can't store blocks in databases; they're a memory-only data type. Later in the article, though, I'll show how
to save a block as a character string, and how to convert a character string to a block. You can store
characters in databases, therefore you can, in effect, store blocks. So what type of data does a block contain?
That's a good question and one that takes some explaining. In the xBase world, we're used to variables
containing data. We use variables to save things like account balances, invoice dates, and customer names.
Well, code blocks don't contain data--they contain compiled program code. To be more exact, code blocks
store compiled Clipper expressions.
Code blocks and macros

The most effective way to describe a code block is to compare it to the character string you use with the
macro operator. Consider the following code fragment:

a = 1
b = 2
C = "a + b"
? & c

We expect the ? command to generate the value 3, the sum of variables a and b. The type of variable c is
character. As far as Clipper is concerned, you want to copy the character string "a + b" to the variable c. As
programmers, we know that "a + b", is a string formatted as a character expression. Clipper doesn't however,
and in Summer'87 we can't indicate it.

The macro operator converts a character string to a literal. When you write:

c = "a + b"
? & c

the macro operator acts as if you've written:

? a + b

Now, if you had written this, Clipper would have compiled the code to add the variables a and b, and inserted
this code in your object file. When the runtime engine encountered the code, it would add the variables and
display the result.

You didn't write it though, so the macro operator has to perform both jobs. It takes the character variable and
converts it to program code. In this case, it generates code to add a to b, then executes the code. In
converting a character string to a literal, the macro operator performs two steps:

1. Compiles the character expression into program code


2. Runs the compiled code

Compiling is usually a function of the compiler, but in this case, the macro operator compiles the code while
the program is running. That means that a big chunk of the Clipper compiler is also present in your executable
program.

Now, in this case, we knew when we compiled the program that we needed code to add a to b, but we couldn't
tell the compiler that; in Summer 87 we don't have a "macro' or program code" type. But we do in 5.0 code
blocks. Code blockscontain compiled expressions. As an example, let's rewrite the addition
using code blocks:

a = 1
b = 2
c = { || a + b}
? eval (c)

Let's go over the incredible syntax that you use to create a code block. You start the code block with { and
end it with }. I'll tell you what the two I characters (you'll find them above the \ key on most keyboards) mean
later in this article; for now just believe me--they have to be there.

It's important to understand who's doing what. The compiler compiles the code to add a to b. The runtime
system copies the code that adds a to b into the variable c. After the assignment, c's type is B' for block. It
contains program code to add a to b, and it's a regular variable. Now--and this is important to note--the
assignment does not add a to b. It just copies the compiled expression into the variable c, just as when you
write:

open = .F.

Clipper's runtime system copies the constant .F. to the variable open.

To evaluate a code block's expression, you pass it as a parameter to a new function, EVAL(). EVAL() evaluates
the expression, and returns its result. Here it runs the code to add a to b, and returns the result, 3. There's
nothing magical about EVAL(). It's just another built-in function like STR() and DTOC().

Code blocks can contain many expressions. You simply separate them with commas as shown here:

cb = { || a(), b(), c() }


When you pass cb to EVAL(), Clipper first calls the function A, then the function B, and finally the function C.
EVAL() returns the result of the last expression evaluated, in this case C's return result.

One key difference between code blocks and macros is that the macro operator always compiles and runs, and
the two steps are indivisible. With code blocks, the compiling is done by the compiler, the running by EVAL().
The two steps are separate, and as you'll see, this is a big advantage.

Repetitive macro expansion

Repeatedly macro-expanding a character string can be slow. As an example, consider:

filt = "lname = 'Spenc'"

DO WHILE &filt
.
.
.
SKIP
ENDDO

Here we're processing a range of records where the lname field is equal to the constant "Spence". Assume
there are 100 such records. Every time Clipper examines the WHILE loop header, it compiles the string filt
to code to compare the lname field with the string constant "Spence", then runs it. Note that Clipper compiles
the code 100 times, even though it always compiles to exactly the same thing. Using the macro operator, we
have no choice. It always compiles and runs, and the two steps are indivisible.

You'd write the same code using code blocks like this:

filt = { || lname = "Spence" }

DO WHILE eval(filt)
.
.
.
SKIP
ENDDO

The code to compare the lname field to the constant "Spence" is generated once, by the compiler. It's re-
evaluated by the loop header using the EVAL().

Delayed code evaluation

Code blocks thus allow you to use a variable to hold a sequence of code that you'll eventually execute. For
example, consider this:

a = 1
b = 2
cb = { || a + b }
? eval(cb) // 3
a = 4
b = 3
? eval(cb) // 7

The code block cb contains the expression to add the variables a and b. The first time we evaluate it, Clipper
returns the value 3. We then change the values of a and b, without changing the code block, and re-evaluate
the block. Clipper returns the value 7.

Code block parameters

You define functions and procedures in terms of formal parameters. When you call the routine, you pass actual
parameters, and Clipper's runtime system copies them into the formal parameters. The biggest advantage to
using parameters is you can parameterize a common sequence of code with regard to a number of variables.
You can do the same with code blocks. You define one with formal parameters, then when you evaluate
the block, you supply the actual parameters. You define the formal parameters between the two I characters
(that's why they're there), as in:

c = { | a, b | a + b }

This defines c as a code block that accepts two parameters. We've called them a and b, but just like the
formal parameters of functions and procedures, you can call them anything.
You supply actual parameters to the block when you evaluate it, as in:

? eval(c, 7, 5)
The runtime system substitutes 7 for a and 5 for b, and EVAL() thus returns 12.

You can update a variable from inside a code block using the inline assignment operator, For example:

a=1
cb = { || a := 3 }
eval (cb)
?a // 3

Since the code block contains expressions, if you use the operator, Clipper would compare a to 3. The inline
assignment operator always performs an assignment.

By default, parameters are always passed to code blocks by value, which means they can't be changed, as in:

a = 1
cb = { | pa | pa := 4}
eval (cb, a)
? a // 1

However, you can pass parameters to code blocks by reference by preceding the actual parameter with the @,
as shown here:

a=1
cb = { | pa | pa := 4}
eval(cb, @a)
?a // 4

You can, of course, pass these parameters along to a function, as in:

cb = { | p1, p2 | func(p1, p2) }


.
.
.
eval( cb, var1, var2)

You can then evaluate cb passing parameters, which are in turn passed to the function FUNC(). This would be
cumbersome to implement using macros.

Iterator functions

AEVAL() is a function you can use to process an array quickly. You pass it an array as its first parameter, and
a code block as its second. AEVAL() loops through the array, passing each element in turn to the code block.
The important point to note is that AEVAL() does the looping, which is much faster than writing your own loop.

As an example of using AEVAL(), consider:

ar = { "Spence", "Tollefson", "Micou" } // Array with


// 3 initialized elements
aeval( ar, {| elem | qout(elem)} )

AEVAL() loops through the ar array, passing each element, in turn, to the code block. The code block is thus
invoked once for each element in the array and receives the element as a parameter. The code block calls the
formal parameter elem (you can call it anything), which it passes to the QOUT() function. The first time
the block is invoked, AEVAL() passes ar[1], the character string "Spence", to the block. The block refers to it
as elem, which it passes to QOUT(). When the block returns, AEVAL() reinvokes it with the next array element,
ar[2], the character constant "Tollefson". Again, the block simply passes this to QOUT() and returns. AEVAL()
then invokes the block for the last time, passing the character string "Micou". After this, AEVAL() returns and
Clipper evaluates the next line of code.

The previous call to AEVAL() is thus a more efficient way of writing:

FOR i = 1 TO len(ar)
qout(ar[i])
NEXT

QOUT() is the functional equivalent of the ? command, so the AEVAL() call prints each element of the array.

You can use AEVAL() to add flexibility to existing routines. For example, assume you have a routine to execute
a report, "do_reps." It takes a report name as a parameter. If you need to execute many reports, you have to
call do_reps once for each report. Using AEVAL() you can pretend that do_reps accepts an array of report
names, using:
aeval( { "Report 1", "Report 2", "Report 3" ), ;
{ | this_rep | do_reps(this_rep) } )

This calls do_reps once for each report, but we write it in a very efficient way.

To add many elements to the end of an array, ar, you use the AADD() function with AEVAL(), as in:

aeval({"Spence", "Tollefson", Micou"}, ;


{ | elem | aadd(ar, elem)})

or more generically as a compiler macro:

#define AADDMANY( ar, elems) ;


aeval( elems, { | elem | aadd( ar, elem )})
.
.
.
AADDMANY( ar, ("Spence", "Tollefson", Micou"} )

The point is that you can use a compiler macro to make it appear as though any function takes an array as a
parameter--you just call it once for each element, using AEVAL() to do the looping.

DBEVAL() is to databases what AEVAL() is to arrays. It loops through a range of database records, invoking a
user-supplied code block for each record. You pass it a number of parameters corresponding to the filters you
use on batch commands. You can specify the following clauses:

FOR condition
WHILE condition
NEXT number of records
RECORD number
REST

It's interesting to note that many built-in commands, such as COUNT, SUM, and AVERAGE, are implemented
using DBEVAL(). If you look in the STD.CH file included with 5.0, you'll see the corresponding user-defined
commands translate to calls to DBEVAL().

You could always write your own loops to perform built-in commands such as COUNT. The reason you didn't is
because the built-in commands operated faster. Well, now you can write your own record processing routines
that operate as quickly as the built-in commands--after all, they both use DBEVAL(). More importantly though,
you can now perform several operations on a subset of records with only one pass of the database, and without
the overhead of your looping code. You use DBEVAL() to move the record pointer, check the conditions etc.,
and the code block to perform many jobs. Remember, a code block can contain several expressions. The
example below shows this. It processes all December invoices, counting the number of invoices and summing
the due field:

USE invs INDEX inv_date //Indexed on inv_date


num_recs = 0
tot_due = 0
// Save SOFTSEEK and turn it on
save_soft = set(_SET_SOFTSEEK, .T.)
// Find first invoice in December
SEEK ctod("12/01/90")
dbeval( { || num_recs++, tot_due = invs->due),, ;
{ || invs->inv_date <= ctod("12/31/90")},,,.F.)
// Restore SOFTSEEK setting
set(_SET_SOFTSEEK, save_soft)

DBEVAL()'s third parameter is the WHILE scope. Our WHILE scope is "WHILE the month is December," which
we code as:

{|| invs->inv_date <= ctod("12/31/90") }

As you can see, we must pass the scope as a block. DBEVAL()'s second parameter, which we don't pass, is the
FOR scope. In this example, DBEVAL() invokes our block for each record in the scope. It moves the record
pointer, ensuring scopes are maintained, etc. As soon as the WHILE condition returns a false, DBEVAL() stops.
Inside our code block, we increment the counter, num_recs, and the due field to the tot_due variable.

To write this in Summer '87 you would either have to code two batch commands (COUNT and SUM), making
two passes of the database, or write your own WHILE loop. Calling DBEVAL() is much faster than either of these
two alternatives.
Compiling a code block at runtime

Sometimes you want to use a code block but don't know what it is until runtime. Maybe the user enters a filter
condition and you want to implement it as a block rather than repeatedly expanding a character string using
the macro operator. You thus need a way of converting a character string to a code block while the program is
running.

Well, you may be surprised to hear that you use the macro operator to convert a character string that looks like
a code block into a real code block, as in this example:

filt = "lname = 'Spence"'


filt = "{||" + filt + "}"
// filt is now a specially-formatted character
// string; it looks like a code block
Filt_b = &filt
// filt_b's type is now block
DO WHILE eval(filt_b)
.
.
SKIP
ENDDO

First, we make the variable filt look like a code block by adding the initial characters t I I " and the trailing
character "}". Then we use the macro operator to convert it to a block. If you think about it, this is logical.
Remember, the macro operator converts a character string to a literal, so when you write:

filt = "(|| lname = Spence')"


filt_b = &filt

the macro operator makes it appear as if you had written:

filt_b = (^^ lname = 'Spence'}

When you construct a code block with the macro, of course, the compiling is now being done at runtime.
However, it's only done when you expand the macro, not every time you use it. Thus, you still benefit from
splitting the compiling of the code from its evaluation.

You can write a compiler macro to make this conversion easier to use:

#define COMPILE( cexpr ) ( "(||" + cexpr + "}" )

Then use it with:

i_cb = COMPILE( indexkey() )

This gives us a code block, such that when it's evaluated, it returns the current index key value.
Finally, though you can't store blocks in databases, you can store their character representation. Use the
compile macro to convert them to a block when you need them.

Summary

I hope you now understand more about what a code block is and that you won't be intimidated by its daunting
syntax. We've covered how you can use a code block to replace repetitive macro expansion and how you can
use the two iterator functions, AEVAL() and DBEVAL(), to replace loops, making your code more efficient.

The true benefit of code blocks is in parameterizing the logic of a routine. You can see this to a certain extent
with DBEVAL(). We not only specify what data it operates on, but also how it operates. This allows for
great code reusability.

Next time we'll look at arrays in more detail. I'll show how 5.0 has extended two built-in functions, ASCAN()
and ASORT(), to take an optional block parameter. By passing the block, you can tell ASCAN() how to search
and ASORT() how to sort.

Rick Spence was a member of the Nantucket development team. His Clipper5.0 book, The Clipper
Programming Guide, Second Edition, has been published by Data Based Solutions, inc. and Slawson
Communications. Rick is currently touring the U.S., giving 5.0 training. You can contact him through
his company, Software Design Consultants, at (818) 892-3398 or on MCI (LSPENCE).

http://www.accessmylibrary.com/coms2/summary_0286-9227615_ITM

You might also like