Closures and Generators in Visual FoxPro

Josip Zohil, Koper, Slovenija, june 2013

 

In last years many programming languages have itroduced closures, generators and procedural (x-base) programming style. Composing code using  events is sometimes very difficult, especially in programming asynchronous processes. The solution is in composing programs (procedures, functions). In this article I shall show you how to use closures and generators in VFP.

Closure

 

Let us start with an example:

The function:

Function f3()

W=X+Y+Z

Endfun

Is a function with no parameters and a local variable W. The variables X, Y and Z can be, for example:

-        A field in an open data table (from the environment),

-        A variable defined in a parent function (method).

We can use the function f3 in this way:

X=1

Y=2

Z=7

?f3()

The function f3 does not contain  »all the elements«. In some way it is invalid. But when we add it  the values X=1, Y=2 and Z=7 we have a closed system, a  closure. In the new environment the function is not »invalid«, it executes in a correct way. We are interested in the method, how to use the closure.

 

Generators

 

In programming languages such as C#, F#, JavaScript, Pyton you create generators using the keyword yield (and other techniques). In VFP it is much simpler. For example, a counter function:

Function counter()

i=i+1

Endfun

 

And its use:

i=0      && after this initialization the variable x (the state) is hidden.

?counter()  && result is 1        && stop (suspend) execution

?'Other computation', 19+22   && jumps to another task

? Counter ()  && result is 2       && resumes from a stop

The explanation:

A function counter () takes the value for i from its environment, increase i by one (i+1) and »writes« its value »in the environment«. The second time it takes the value i=1 from the environment and increase it (i+1) to 2 and so on.

A function counter generates values, so we say it is a generator (producer). The function counter is implemented using the closure. The closure is a programming technique (we can ignore its mathematical definition).

 

State

 

In our last example the variable i is a state variable. After the initialization it is hidden, we do not manage it; it is managed by the logic (function) counter.

When we run the function counter (the generator), it calculates the new state (0+1) and stop. On the second call, it continues (resumes) from this new state. We can call it also after ten minutes, it will increase the old value 1 to 1+1=2.

 

Note. Remember this special behavior: we run the function, stop, do another task and resume from where we have stoped. It seems the function has multiple entry (and exit) points: the first is with value 0, the second with value 1 and so on.

 

Recursion

 

Some programmers prefer to write functions in a recursive manner. Let us write the counter as a recursive function:

 

Function rcounter(i,n)

i=i+1  &&change state

?i

If i>n  && test stop

RETURN i

endif

RETURN rcounter(i,n)  &&loop with a new state

endfunc

We test a rcounter function:

?rcounter(0,3)

Return

 

The resursive function rcounter change the initial state (i), after that it checks for exit and continues to loop with a new state (new value for i).

Note. The integer n has to be less than 128. The generator counter has not such limit.

 

Let us write the recursive function rcounter as a non recursive:

Function cocounter(k,n)

i=k

DO WHILE .t.

If i>n  && test stop

RETURN i

ENDIF

counter()  && generates new values

enddo

endfunc

 

We use the function cocounter in this way:

?cocounter(0,3)

Return

 

A cocounter takes an initial value k and the numbers of »loops« n as its arguments and bounce a counter for n-k times. We have no possibility to suspend or cancel the loop in a programming way. We are forcing the counter to execute n-k times. We call this technique force a generator.

 

Composing generators

 

Generators in VFP become really interesting when we compose them using closures. Let us start with a data table example.

We have a report of two tables. We would like to parallelize theirs report: report the first element of the first table, then the first element of the second and so on.

 

Function parrepo()

Select 0

Use table1

Select 0

Use table2

le1

Do while not eof()

*resume, continue  fun1

Fun1()

*suspend, pause fun1

*resume, continue fun2

Fun2()

*suspend, pause fun2

enddo

endfun

function fun1()

Select table1

?id, quantity

Skip

endfun

function fun2()

?name,country

skip

endfun

 

A function fun1 is a generator. It selects a table (state), returns nothing, write on the screen and modify state (skip). After that it pauses and pass control to the function fun2. It writes, modify the state and pass control to fun1. We are manipulating two tables in parallel. We can look at this as an asynchronous process. A function fun1 on first call pause and get a callback when fun2 yield (return control, not value). After that fun1 continue its execution.

A parrepo() is a synchronization function: is synchronize two processes fun1 and fun2 in a particular way. They are executing one after the other (serially). The two processes are never concurrent!!!

 

The function genrepo is a coordinator (scheduler). It coordinates the function fun1 and fun2 using theirs suspension (pause) points. From a programmer point of view  she/he has to manage also suspensions (pauses).

Note: you manage two non concurrent kids on a trampoline between pauses. One stop bounce and the other start (continue).

Our coordination genrep is very composable. We can create a report (inside a do loop) also in this way:

Fun1()

Fun2()

Fun2()

 

This new composition means: write data from the first table, skip, switch to the second table, write data, skip, write data, skip, switch to the first table and so on.

 

Time intervals coordination

 

Another method of coordination is by timers. Let us see an example:

 

Let us modify the fun1 in a time dependent function:

Function Funtime1()

T1=seconds()

Select table1

Do while i<10000 and seconds()-t1<0.1 and not eof()

?id, quantity

Skip

enddo

endfun

 

In a similar way modify also fun2().

In the function funtime1() we force the execution (the record skip) for at most 10000 times or less if this process takes more than 0.1 seconds. When we run this program:

Funtime1()  && first

Funtime2()

Funtime1() && second

 

We run Funtime1()  2 times. The second time it starts executing at a record it leaves at first call. The beauty of generators is theirs composability: we can suspend them programmatically or with other tools (bounded time interval) and we can resume them in an easy way.

 

Function call and generator

 

Let us compare this two similar functions:

 

Function funcall(k)

Return k+3

Endunc

Function gencall()

K= k+3

Endunc

 

The first is a normal function, the second is a generator (closure). We call them for two times:

? funcall(4)    &&   7

? funcall(4)    &&   7

K=4

gencall()

gencall()

?k   &&   10

 

We call two similar functions two times and they generate two different final results (7 and 10). The first function funcall does not read from the state (environement), the second gencall reads and writes »the state«.

 

Table as state

 

Let us modify a function gencall to tabcall():

Function tabcall()

X=table1.quantity

Replace  table1.quantity with x+1

Endfunc

 

We call the function tabcall() two times

Select 0

Use table1

? table1.quantity   && 6

Tabcall()

M=m+3

Tabcall()

? table1.quantity   && 8

 

This function is a generator (it returns nothing), it only changes state.

In programming languages that support X-base programming you can use generators!!!

 

Accumulator  function

Accumulator using the VFP commands

 

We frequently use a sum command or a SQL statement to accumulate values in a table, such as:

1) use test

SUM quantity**2 TO acc

?acc

2) Select sum(quantity**2) as total from test into cursor xxx

Brow

 

We have passed a function sum(quantity**2) to the two commands. This function is without a name, we say it is anonymous. Let us explain this.

Normally, we use named functions, for example the function:

f(x)=x**2

with the name f transforms the values for (x) into theirs' squares.

Sometimes we use the lambda notation: fun: x -> x**2 or frequently in VFP as anonymous function : »x**2« (We don't pass the anonymous function as in functional languages, but as a string).

 

Accumulator using the functional style

 

In functional programming languages ( and in recent years also in object oriented languages) you use functions fold, map, select (LINQ), filter and objects (iterators, generators). Let us see an example, how to create an accumulator (reductor) in VFP to sum the doubled values of an array.

 

The function accum has three parameters: we pass it the array, the function and the initial value of the accumulator variable. It gives us (return) the accumulated value.

 

FUNCTION accum(arr,f,acc)

FOR EACH x IN arr

acc=acc+EVALUATE(f)  && lazy evaluated: on each element

loop

ENDFOR

RETURN acc

Endfunc

 

FUNCTION f2 ()  && f2 is a closure, it takes the value x from the environment

RETURN "x**2"  && x is an element; the lambda notation is: fun: x-> x**2

ENDFUNC

 

The function f2 operate on the array elements (it calculates the square). This function is lazily evaluated: it is calculated when the »for loop« reach an element, for example, when the loop reaches the array element 2, it takes the element's value and calculate the square and add it to the accumulated value.

The function f2 do nothing; it is only logic. It takes nothing and returns a string containing logic. It is like an object, but less expensive.

 

We use the accum function in this way:

* example accumulate (reduce) function: the sum of elements square

* pay attention where the variables are defined (their scope)

DIMENSION ar[3]

ar[1]=5

ar[2]=8

ar[3]=3

?accum(@ar,f2(),0)   && pass a function f2()

?accum(@ar,"x**2",0)  && as an anonymous function (without name)

RETURN

 

We can pass the function f2() to the function accum in various ways. We have presented two:

-        pass a function by name;

-        pass a function without name (as an anonymous function) (a VFP style).

 

This style of writing the commands is similar to VFP; you pass a function and VFP like in a select SQL command iterate the array and compute the result. The function f2 is a closure; it takes the value x from its  environment.

 

Passing an array

 

This function is without a parameter »arr«:

FUNCTION accumclosure(f,acc)

FOR EACH x IN arr

acc=acc+EVALUATE(f)  && lazy evaluated: on each element

loop

ENDFOR

RETURN acc

Endfunc

When it executes:

? accumclosure("x**2",0)

It calculates the accumulated values. It takes the variable arr from the environment.

Normally we pass an array by value or by reference. We have also a less known technique, a closure.

 

Next and rest

 

Let us analyze the for loop  in the function accum:

FOR EACH x IN arr

acc=acc+EVALUATE(f)  && lazy evaluated: on each element

loop

ENDFOR

Note. The function has side effects, if its output doesn't depend only on its input values. For example, the function g(x)=x+quantity, where quantity is a field in a VFP table, has a side effect. The table can be modified and as a consequence the function values can change.

 

This »for loop« depends only on the function f. In case it is a pure function (without side effects) a smart compiler can optimize this loop.

It is easy to drop this function in smaller task, such as: Accumulate the first ten elements, then the next ten and so on. At the end sum all the partial results. You can compute all the task in parallel.

Instead of the array we can have an »infinite« stream: In such a case we think, for example in this way: accumulate the first ten values and then the rest (the continuation).

 

Conclusion

 

In VFP we use the functional style techniques. In this article I have presented the closure and generators »techniques« and theirs particularities. Closure, generators and state machines are elements in managing coroutines (in VFP terminology programs, methods and functions).

In the next article we are going to manage VFP coroutins to add VFP asynchronous capabilities.