Interfaces funcionales ... en VBA

"... 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:

Nombre del métodoParámetrosResultado
isGen-Devuelve True si el objeto es un generador.
isCont-Devuelve True si el objeto es un contenedor.
getCont-Para un contenedor devuelve una matriz local, para un generador devuelve Vacío
getNext-Devuelve el siguiente valor
hasNext-Devuelve True si el siguiente valor está presente
InitiniVal como variante, lambda como cadena = "", emptyC como booleano = falsoiniVal - valor inicial;
lambda - función anónima para generador
emptyC: cuando se establece en True, se crea un contenedor vacío
Tomarn como enteroDevuelve un contenedor que contiene n valores consecutivos obtenidos del objeto fuente
Filtrolambda como cuerdaDevuelve el objeto obtenido al filtrar el original de acuerdo con la función lambda sin nombre
Mapalambda como cuerdaDevuelve el objeto obtenido al mapear el original de acuerdo con la función lambda sin nombre
Reduciracc como variante, lambda como cadena,Devuelve el resultado de la convolución del objeto actual con el valor inicial de la batería acc y la función de plegado especificada por el parámetro lambda
tomar mientrasn como entero,
lambda como cadena
Devuelve un contenedor que contiene n (o menos) valores consecutivos que satisfacen el predicado especificado por la función lambda sin nombre
caerMientrasn como entero,
lambda como cadena
Devuelve un contenedor que contiene n (o menos) valores consecutivos obtenidos del original después de omitir valores que satisfacen el predicado especificado por lambda.
cremalleraiter as aTER,
n Como entero = 10
Acepta un contenedor o generador, y devuelve un contenedor que contiene pares de valores, desde el contenedor base y desde el contenedor contenedor. El tamaño de resultado predeterminado es diez.
zipWithiter as aTER,
lambda como cadena,
n Como entero = 10
Toma un contenedor y una función anónima de dos argumentos. Devuelve un contenedor que contiene los resultados de aplicar la función especificada a pares sucesivos: un valor del contenedor base y el otro del contenedor de parámetros.
Tamaño-Para un contenedor, devuelve el número de elementos.
summa-Suma de valores de contenedor
produccion-Producto de valores de contenedor
maximo-El valor máximo en el contenedor.
mínimo-El valor mínimo en el contenedor.

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 !!!

Source: https://habr.com/ru/post/455347/


All Articles