"... aquellos que no son reacios a mirar a un aficionado que culpa públicamente a un tonto, que observen cómo pruebo que Java y Visual Basic son gemelos separados al nacer, y C ++ ni siquiera son un pariente lejano".Bruce McKinney "Die Hard Basic Basic"Introduccion
Un interés constante en los enfoques de programación funcional actualmente conduce al hecho de que los lenguajes de programación tradicionales están adquiriendo activamente medios funcionales. Y aunque los lenguajes funcionales puros aún no son muy populares, la funcionalidad está firmemente establecida en lenguajes como C ++, Java, JavaScript, Python y otros. Sin embargo, el lenguaje VBA ha sido popular entre un público bastante amplio de usuarios de Microsoft Office durante muchos años. Este lenguaje prácticamente no contiene herramientas funcionales.
Intentemos llenar este vacío: propongo una implementación completa (aunque quizás no perfecta) de interfaces funcionales implementadas por VBA. La implementación puede servir como base para posteriores mejoras y mejoras.
Problema de argumentos funcionales
El primer problema que enfrentaremos en este camino es el problema de pasar argumentos funcionales a una función o método. El lenguaje VBA no contiene las herramientas apropiadas (el operador AddressOf solo sirve para transferir direcciones a las funciones API de Windows y no es completamente seguro de usar). Lo mismo puede decirse sobre el conocido método de invocar funciones por puntero (G. Magdanurov Visual Basic en la práctica San Petersburgo: "BHV Petersburgo", 2008). No nos arriesguemos: solo utilizamos funciones de lenguaje estándar y bibliotecas estándar en la implementación.
Desafortunadamente, aquí la OLP no ayuda mucho. Para transferir un objeto funcional a un procedimiento o función, VBA ofrece una oportunidad estándar: envolver la funcionalidad necesaria con un shell de objeto (crear un objeto, uno de cuyos métodos será la funcionalidad necesaria). Se puede pasar un objeto como parámetro. Este enfoque es viable, pero muy pesado: para cada funcionalidad necesaria tendrá que crear su propia clase y un objeto de esta clase.
Hay otra forma, que es mucho más simple y no requiere la creación de clases separadas para cada funcionalidad.
Suponga que desea pasar una función anónima a un determinado procedimiento de proceso que incrementa su argumento en uno. Esta función se puede escribir de la siguiente manera:
x -> x+1
Esta notación de asignación de funciones anónimas ahora se ha convertido casi en el "estándar de facto". La única forma de pasar dicha función a un parámetro es usar una representación de cadena:
r=proc(a,b,”x->x+1”)
aquí ayb son parámetros ordinarios, y el tercer parámetro es una función sin nombre, que es muy clara y difiere poco de las entradas en lenguajes de programación populares.
Para usar una función anónima definida de esta manera, primero debe llevarla a la forma estándar de la función VBA. Esto realiza el siguiente procedimiento de utilidad:
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
La función selecciona una lista de parámetros y el cuerpo de cálculo, y luego forma una función llamada self. Para nuestro caso, la función self tendrá la siguiente forma:
function self(x) self=x+1 End function
Obviamente, de acuerdo con la sintaxis de VBA, esta función hará exactamente lo que debería haber hecho la función anónima: aumentar el valor de su argumento en 1. Verdadero, esta función aún no es una función de VBA, sino solo una línea que contiene el código especificado. Para convertir una cadena en una función, puede usar la biblioteca estándar de Microsoft "Msscript.ocx". Esta biblioteca COM le permite ejecutar código VBA arbitrario especificado en forma de cadena. Para hacer esto, haga lo siguiente:
- Crear un objeto ScriptControl
- Llamar al método de instalación del lenguaje (VBScript);
- Llamar al método de carga de la función;
- Llame al método eval para hacer la llamada.
Todo se parece a esto:
Set locEv=new ScriptControl locEv.Language = "VBScript" locEv.AddCode prepCode(“x->x+1”) r=locEv.eval(“self(5)”)
Después de ejecutar este código, el valor de la variable r será 6.
Aquí deben hacerse tres puntos:
- El cuerpo de una función anónima puede contener varias líneas. Las declaraciones individuales en este caso terminan con un punto y coma. Del código final, los caracteres ";" están excluidos Un cuerpo de varias líneas le permite implementar funcionalidades muy avanzadas en funciones anónimas;
- El hecho de que la función anónima "en realidad" tenga el nombre "self" da una ventaja inesperada: una función anónima puede ser recursiva.
- Dado que el objeto ScriptControl admite dos idiomas: VBScript y Jscript, la función anónima puede escribirse (en teoría) en Jscript (quienes lo deseen pueden probarlo).
A continuación, se describirá un modelo de implementación de objetos.
Modelo de objeto
La base del modelo son objetos de dos tipos: Contenedor y Generador. El objeto Contenedor es un repositorio de una matriz de tamaños arbitrarios, el objeto Generador, como su nombre lo indica, implementa un generador de forma general.
Ambos objetos implementan la interfaz posterior, que se describe con más detalle a continuación. La interfaz incluye 19 funciones:
Para un objeto generador, varios métodos no se implementan directamente; primero debe seleccionar un cierto número de valores en un contenedor. Al intentar llamar a un método no realizado para un generador, se genera un error con el código 666. A continuación, se considerarán varios ejemplos de uso de las interfaces descritas.
Ejemplos
Impresión de números consecutivos de Fibonacci:
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
Aquí se crea un generador con valores iniciales 0 y 1 y una función generadora correspondiente a la secuencia de Fibonacci. A continuación, los primeros 50 números se imprimen en un bucle.
Mapa y filtro:
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
Un contenedor se crea e inicializa mediante una secuencia numérica del rango de 1 a 100. A continuación, los números con el mapa se reemplazan por el inverso. De estos, se toman los veintiuno. Luego, esta población se filtra y se seleccionan números mayores que 0.3 o menores que 0.1. El resultado se devuelve en el contenedor, cuya composición se imprime.
Usando convolución:
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
Aquí, usando la convolución, se considera la suma y el producto de números del 1 al 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
En este ejemplo, el generador co1 se construye, dividiendo secuencialmente el número original por grados 10. Luego, los cocientes se seleccionan hasta que aparezca cero. Después de eso, la lista resultante de cocientes se muestra por la función de tomar el resto de la división por 10. El resultado es una lista de dígitos del número. La lista se resume, calculó el máximo, mínimo y producto.
Conclusiones
El enfoque propuesto es bastante viable y puede aplicarse con éxito para resolver las tareas cotidianas de un programador de VBA en un estilo funcional. ¿Por qué somos peores que los javistas?
Descargue ejemplos
aquíBuena suerte !!!