"... those who are not averse to gawking at an amateur to fool around publicly, let them watch, as I argue that Java and Visual Basic are twins separated at birth, and C ++ is not even a distant relative."Bruce McKinney “Die Hard Visual Basic”Introduction
Constant interest in functional programming approaches currently leads to the fact that traditional programming languages are actively acquiring functional means. And, although pure functional languages are still not very popular, the functionality is firmly established in languages such as C ++, Java, JavaScript, Python, etc. VBA has been popular with quite a few Microsoft Office users for many years, but This language contains almost no functional tools.
')
Let's try to fill this gap - I offer a complete (though perhaps not flawless) implementation of functional interfaces implemented by means of VBA. The implementation can serve as a basis for subsequent improvements and improvements.
The problem of functional arguments
The first problem we will encounter on this path is the problem of passing functional arguments to a function or method. The VBA language does not contain the appropriate means (the AddressOf operator is only used to transfer addresses to the Windows API functions and is not completely safe to use). The same can be said about the well-known method of calling functions by a pointer (Magdanurov GI Visual Basic in practice, St. Petersburg: BHV Petersburg, 2008). Let's not take the risk - we use only standard language features and standard libraries for implementation.
Unfortunately, the PLO will not do much for us here. To transfer a functional object to a procedure or function, the VBA language offers a standard opportunity to wrap the required functionality with an object wrapper (to create an object, one of the methods of which will be the necessary functionality). The object can be passed as a parameter. This approach is efficient, but it is very heavy - for each necessary functionality you will have to create your own class and object of this class.
There is another method that is much simpler and does not require the creation of separate classes for each functionality.
Suppose that in some procedure proc you want to pass an anonymous function, which increases its argument by one. This function can be written as:
x -> x+1
Similar notation for defining anonymous functions has now practically become the “de facto standard”. The only way to pass such a function as a parameter is to use a string representation:
r=proc(a,b,”x->x+1”)
here, a and b are the usual parameters, and the third parameter is the nameless function, which is very clear and does not differ much from entries in popular programming languages.
To use an anonymous function defined in this way, it must first be converted to the standard form of the VBA function. This is performed by the following service procedure:
Private Function prepCode(Code As String) As String k% = InStr(Code, "->") parms$ = Trim$(Left$(Code, k% - 1)) body$ = Mid$(Code, k% + 2) If Left$(parms$, 1) <> "(" Then parms$ = "(" + parms$ + ")" If InStr(body$, "self") = 0 Then body$ = ";self=" & body$ & ";" body$ = Replace(body$, ";", vbCrLf) prepCode = "function self" & parms & vbCrLf & body & _ vbCrLf & "end function" End Function
The function selects the list of parameters and the body of the calculation, and then forms a function called self. For our case, the self function will look like this:
function self(x) self=x+1 End function
Obviously, in accordance with the VBA syntax, this function will do exactly what the anonymous function should have done - it increases the value of its argument by 1. True, this function is not yet a VBA function, but only a line containing the specified code. In order to turn a string into a function, you can use the standard Microsoft's Msscript.ocx library. This COM library allows you to execute arbitrary VBA code, specified in string form. To do this, follow these steps:
- Create ScriptControl object
- Call the method of installing the language (VBScript);
- Call the function loading method;
- Call the eval method to execute the call.
It all looks something like this:
Set locEv=new ScriptControl locEv.Language = "VBScript" locEv.AddCode prepCode(“x->x+1”) r=locEv.eval(“self(5)”)
After executing this code, the value of the variable r will be equal to 6.
Three comments should be made here:
- The body of an anonymous function may contain several lines. Individual statements in this case are terminated with a semicolon. The symbols “;” are excluded from the final code. A multi-line body allows for the implementation of highly advanced functionality in anonymous functions;
- The fact that the anonymous function “in reality” has the name “self” gives an unexpected bonus - the anonymous function can be recursive.
- Since the ScriptControl object supports two languages - VBScript and Jscript, the unnamed function can be (theoretically) written in Jscript (those who wish can try).
Next, the implementation object model will be described.
Object model
The model is based on two types of objects: Container and Generator. The Container object is a storage of an array of arbitrary sizes; the Generator object, as the name implies, implements a general form generator.
Both objects implement the aIter interface, which is described in more detail below. The interface includes 19 functions:
For a generator object, a number of methods are not directly implemented - it is necessary to first select a certain number of values into the container. When trying to call an unimplemented method for the generator, an error is generated with the code 666. Next, we will consider several examples of using the described interfaces.
Examples
Printing consecutive Fibonacci numbers:
Sub Test_1() Dim fibGen As aIter Set fibGen = New Generator fibGen.Init Array(1, 0), "(c,p)->c+p" For i% = 1 To 50 Debug.Print fibGen.getNext() Next i% End Sub
Here a generator is created with initial values of 0 and 1 and a generating function corresponding to the Fibonacci sequence. Further in the cycle the first 50 numbers are printed.
Map and filter:
Sub Test_2() Dim co As aIter Dim Z As aIter Dim w As aIter Set co = New Container co.Init frange(1, 100) Set Z = co.map("x -> 1.0/x"). _ take(20).filter(" x -> (x>0.3) or (x<=0.1)") iii% = 1 Do While Z.hasNext() Debug.Print iii%; " "; Z.getNext() iii% = iii% + 1 Loop End Sub
The container is created and initialized with a numeric sequence from the range from 1 to 100. Then the numbers are replaced with the backward ones using the map. Of these, the twenty first are taken. Then this set is filtered and numbers larger than 0.3 or less than 0.1 are selected from it. The result is returned in a container whose composition is printed.
Convolution usage:
Sub Test_4() Dim co As aIter Set co = New Container co.Init frange(1, 100) v = co.reduce(0, "(acc,x)->acc+x") Debug.Print v v = co.reduce(1, "(acc,x)->acc*x") Debug.Print v End Sub
Here, using convolution is the sum and the product of numbers from 1 to 100.
Sub Test_5() Dim co1 As aIter Dim co2 As aIter Dim co3 As aIter Set co1 = New Generator co1.Init Array(123456789), "x -> INT(x/10)" Set co2 = co1.takeWhile(100, "x -> x > 0") Set co3 = co2.map("x -> x mod 10") Debug.Print co3.maximun Debug.Print co3.minimum Debug.Print co3.summa Debug.Print co3.production End Sub
In this example, the co1 generator is built, sequentially dividing the initial number by degrees 10. Then the private ones are selected until zero appears. After that, the resulting list of quotients is displayed by taking the remainder of the division by 10. As a result, a list of digits of the number is obtained. The list is summed, it calculates the maximum, minimum and product.
findings
The proposed approach turns out to be quite workable and can be successfully applied to solving everyday tasks of a VBA programmer in a functional style. Why are we worse than javistes?
Download examples
here.Good luck !!!