"... ceux qui ne sont pas opposés à regarder un amateur blâmer publiquement un imbécile, laissez-les observer comment je prouve que Java et Visual Basic sont des jumeaux séparés à la naissance, et C ++, ils ne sont même pas un parent éloigné."Bruce McKinney «Die Hard Basic Basic»Présentation
Un intérêt constant pour les approches de programmation fonctionnelle conduit actuellement au fait que les langages de programmation traditionnels acquièrent activement des moyens fonctionnels. Et bien que les langages fonctionnels purs ne soient toujours pas très populaires, la fonctionnalité est fermement établie dans des langages tels que C ++, Java, JavaScript, Python, etc. Le langage VBA est populaire auprès d'un large public d'utilisateurs de Microsoft Office depuis de nombreuses années, cependant ce langage ne contient pratiquement aucun outil fonctionnel.
Essayons de combler cette lacune - je propose une implémentation complète (mais peut-être pas parfaite) des interfaces fonctionnelles implémentées par VBA. La mise en œuvre peut servir de base pour des améliorations et améliorations ultérieures.
Problème d'arguments fonctionnels
Le premier problème que nous rencontrerons le long de ce chemin est celui de la transmission d'arguments fonctionnels à une fonction ou une méthode. Le langage VBA ne contient pas les outils appropriés (l'opérateur AddressOf ne sert qu'à transférer des adresses aux fonctions de l'API Windows et n'est pas complètement sûr à utiliser). On peut en dire autant de la méthode bien connue d'appel de fonctions par pointeur (G. Magdanurov Visual Basic dans la pratique Saint-Pétersbourg: «BHV Petersburg», 2008). Ne prenons pas de risques - nous utilisons uniquement des fonctionnalités de langage standard et des bibliothèques standard dans l'implémentation.
Malheureusement, ici, l'OLP n'aide pas beaucoup. Pour transférer un objet fonctionnel dans une procédure ou une fonction, VBA offre une opportunité standard - d'envelopper la fonctionnalité nécessaire avec un shell d'objet (créer un objet, dont l'une des méthodes sera la fonctionnalité nécessaire). Un objet peut être passé en paramètre. Cette approche est réalisable, mais très lourde - pour chaque fonctionnalité nécessaire, vous devrez créer votre propre classe et un objet de cette classe.
Il existe une autre méthode, beaucoup plus simple et ne nécessitant pas la création de classes distinctes pour chaque fonctionnalité.
Supposons que vous souhaitiez passer une fonction anonyme à une certaine procédure proc qui incrémente son argument d'une unité. Cette fonction peut s'écrire comme suit:
x -> x+1
Cette notation de l'attribution de fonctions anonymes est maintenant presque devenue la «norme de facto». La seule façon de passer une telle fonction à un paramètre est d'utiliser une représentation sous forme de chaîne:
r=proc(a,b,”x->x+1”)
ici a et b sont des paramètres ordinaires, et le troisième paramètre est une fonction sans nom, qui est très claire et diffère peu des entrées dans les langages de programmation populaires.
Pour utiliser une fonction anonyme définie de cette manière, vous devez d'abord l'amener à la forme standard de la fonction VBA. Cela exécute la procédure d'utilitaire suivante:
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 fonction sélectionne une liste de paramètres et le corps de calcul, puis forme une fonction appelée self. Pour notre cas, l'auto-fonction aura la forme suivante:
function self(x) self=x+1 End function
Évidemment, conformément à la syntaxe VBA, cette fonction fera exactement ce que la fonction anonyme aurait dû faire - augmenter la valeur de son argument de 1. Vrai, cette fonction n'est pas encore une fonction VBA, mais seulement une ligne contenant le code spécifié. Pour transformer une chaîne en fonction, vous pouvez utiliser la bibliothèque Microsoft standard "Msscript.ocx". Cette bibliothèque COM vous permet d'exécuter du code VBA arbitraire spécifié sous forme de chaîne. Pour ce faire, procédez comme suit:
- Créer un objet ScriptControl
- Appelez la méthode d'installation de la langue (VBScript);
- Appelez la méthode de chargement de fonction;
- Appelez la méthode eval pour passer l'appel.
Tout ressemble Ă ceci:
Set locEv=new ScriptControl locEv.Language = "VBScript" locEv.AddCode prepCode(“x->x+1”) r=locEv.eval(“self(5)”)
Après avoir exécuté ce code, la valeur de la variable r sera 6.
Trois points doivent être soulignés ici:
- Le corps d'une fonction anonyme peut contenir plusieurs lignes. Dans ce cas, les instructions individuelles se terminent par un point-virgule. A partir du code final, les caractères ";" sont exclus. Un corps multi-lignes vous permet d'implémenter des fonctionnalités très avancées dans des fonctions anonymes;
- Le fait que la fonction anonyme "en réalité" porte le nom de "soi" donne un bonus inattendu - une fonction anonyme peut être récursive.
- Étant donné que l'objet ScriptControl prend en charge deux langues - VBScript et Jscript, la fonction anonyme peut être (théoriquement) écrite en Jscript (ceux qui le souhaitent peuvent l'essayer).
Ensuite, un modèle d'implémentation d'objet sera décrit.
Modèle d'objet
La base du modèle sont des objets de deux types: Container et Generator. L'objet Container est un référentiel d'un tableau de tailles arbitraires; l'objet Generator, comme son nom l'indique, implémente un générateur général.
Les deux objets implémentent l'interface aIter, qui est décrite plus en détail ci-dessous. L'interface comprend 19 fonctions:
Pour un objet générateur, un certain nombre de méthodes ne sont pas directement implémentées - vous devez d'abord sélectionner un certain nombre de valeurs dans un conteneur. Lorsque vous essayez d'appeler une méthode non réalisée pour un générateur, une erreur est générée avec le code 666. Ensuite, plusieurs exemples d'utilisation des interfaces décrites seront considérés.
Des exemples
Impression des numéros consécutifs 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
Ici, un générateur est créé avec les valeurs initiales 0 et 1 et une fonction génératrice correspondant à la séquence de Fibonacci. Ensuite, les 50 premiers chiffres sont imprimés en boucle.
Carte et filtre:
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 conteneur est créé et initialisé par une séquence numérique comprise entre 1 et 100. Ensuite, les nombres avec carte sont remplacés par l'inverse. Parmi ceux-ci, les vingt et unième sont pris. Ensuite, cette population est filtrée et des nombres supérieurs à 0,3 ou inférieurs à 0,1 sont sélectionnés à partir d'elle. Le résultat est renvoyé dans le contenant dont la composition est imprimée.
En utilisant la convolution:
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
Ici, en utilisant la convolution, la somme et le produit des nombres de 1 à 100 sont considérés.
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
Dans cet exemple, le générateur co1 est construit, divisant séquentiellement le nombre d'origine par degrés 10. Ensuite, les quotients sont sélectionnés jusqu'à ce que zéro apparaisse. Après cela, la liste résultante de quotients est affichée par la fonction de prendre le reste de la division par 10. Le résultat est une liste de chiffres du nombre. La liste est résumée, il a calculé le maximum, le minimum et le produit.
Conclusions
L'approche proposée est tout à fait réalisable et peut être appliquée avec succès pour résoudre les tâches quotidiennes d'un programmeur VBA dans un style fonctionnel. Pourquoi sommes-nous pires que les javistes?
Téléchargez des exemples
iciBonne chance !!!