"... aqueles que não são avessos a encarar um amador culpando publicamente um tolo, observem como eu provo que Java e Visual Basic são gêmeos separados no nascimento, e C ++ eles nem são parentes distantes".Bruce McKinney “Die Hard Basic Basic”1. Introdução
Um interesse constante nas abordagens de programação funcional atualmente leva ao fato de que as linguagens de programação tradicionais estão adquirindo ativamente meios funcionais. E embora as linguagens funcionais puras ainda não sejam muito populares, a funcionalidade está firmemente estabelecida em linguagens como C ++, Java, JavaScript, Python e outras.A linguagem VBA é popular com uma audiência bastante grande de usuários do Microsoft Office há muitos anos. esse idioma praticamente não contém ferramentas funcionais.
Vamos tentar preencher essa lacuna - proponho uma implementação completa (embora talvez não perfeita) de interfaces funcionais implementadas pelo VBA. A implementação pode servir de base para aprimoramentos e aprimoramentos subsequentes.
Problema de argumentos funcionais
O primeiro problema que enfrentaremos nesse caminho é o problema de passar argumentos funcionais para uma função ou método. O idioma do VBA não contém as ferramentas apropriadas (o operador AddressOf serve apenas para transferir endereços para as funções da API do Windows e não é totalmente seguro de usar). O mesmo pode ser dito sobre o conhecido método de chamar funções pelo ponteiro (G. Magdanurov Visual Basic na prática São Petersburgo: “BHV Petersburg”, 2008). Não vamos correr riscos - usamos apenas recursos de linguagem padrão e bibliotecas padrão na implementação.
Infelizmente, aqui a OLP não ajuda muito. Para transferir um objeto funcional para um procedimento ou função, o VBA oferece uma oportunidade padrão - agrupar a funcionalidade necessária com um shell de objeto (crie um objeto, um dos métodos dos quais será a funcionalidade necessária). Um objeto pode ser passado como um parâmetro. Essa abordagem é viável, mas muito pesada - para cada funcionalidade necessária, você terá que criar sua própria classe e um objeto dessa classe.
Existe outra maneira, que é muito mais simples e não requer a criação de classes separadas para cada funcionalidade.
Suponha que você deseje passar uma função anônima para um determinado procedimento proc que incrementa seu argumento em um. Esta função pode ser escrita da seguinte maneira:
x -> x+1
Essa notação de atribuir funções anônimas agora quase se tornou o "padrão de fato". A única maneira de passar essa função para um parâmetro é usar uma representação de string:
r=proc(a,b,”x->x+1”)
aqui aeb são parâmetros comuns e o terceiro parâmetro é uma função sem nome, que é muito clara e pouco difere das entradas nas linguagens de programação populares.
Para usar uma função anônima definida dessa maneira, você deve primeiro trazê-la para o formato padrão da função VBA. Isso executa o seguinte procedimento utilitário:
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
A função seleciona uma lista de parâmetros e o corpo de cálculo e, em seguida, forma uma função chamada self. Para o nosso caso, a função auto terá a seguinte forma:
function self(x) self=x+1 End function
Obviamente, de acordo com a sintaxe do VBA, essa função fará exatamente o que a função anônima deveria ter feito - aumente o valor de seu argumento em 1. É verdade que essa função ainda não é uma função do VBA, mas apenas uma sequência que contém o código especificado. Para transformar uma string em uma função, você pode usar a biblioteca padrão da Microsoft “Msscript.ocx”. Essa biblioteca COM permite que você execute código VBA arbitrário especificado em forma de seqüência de caracteres. Para fazer isso, faça o seguinte:
- Crie um objeto ScriptControl
- Chame o método de instalação da linguagem (VBScript);
- Chame o método de carregamento de funções;
- Chame o método eval para fazer a chamada.
Tudo se parece com isso:
Set locEv=new ScriptControl locEv.Language = "VBScript" locEv.AddCode prepCode(“x->x+1”) r=locEv.eval(“self(5)”)
Após executar esse código, o valor da variável r será 6.
Três pontos devem ser feitos aqui:
- O corpo de uma função anônima pode conter várias linhas. As instruções individuais nesse caso terminam com ponto e vírgula. No código final, os caracteres ";" são excluídos. Um corpo de várias linhas permite implementar funcionalidades muito avançadas em funções anônimas;
- O fato de a função anônima “na realidade” ter o nome “auto” oferece um bônus inesperado - uma função anônima pode ser recursiva.
- Como o objeto ScriptControl suporta duas linguagens - VBScript e Jscript, a função anônima pode ser (teoricamente) escrita em Jscript (quem quiser pode tentar).
A seguir, um modelo de implementação de objeto será descrito.
Modelo de objeto
A base do modelo são objetos de dois tipos: Container e Generator. O objeto Container é um repositório de uma matriz de tamanhos arbitrários; o objeto Generator, como o nome indica, implementa um gerador de formulários geral.
Ambos os objetos implementam a interface aIter, descrita em mais detalhes abaixo. A interface inclui 19 funções:
Para um objeto gerador, vários métodos não são implementados diretamente - você deve primeiro selecionar um certo número de valores em um contêiner. Ao tentar chamar um método não realizado para um gerador, um erro é gerado com o código 666. Em seguida, vários exemplos de uso das interfaces descritas serão considerados.
Exemplos
Impressão 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
Aqui, um gerador é criado com os valores iniciais 0 e 1 e uma função geradora correspondente à sequência de Fibonacci. Em seguida, os 50 primeiros números são impressos em um loop.
Mapa e 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
Um contêiner é criado e inicializado por uma sequência numérica do intervalo de 1 a 100. Em seguida, os números com o mapa são substituídos pelo inverso. Destes, os vinte primeiros são tirados. Em seguida, essa população é filtrada e números maiores que 0,3 ou menores que 0,1 são selecionados nela. O resultado é retornado no contêiner, cuja composição é impressa.
Usando convolução:
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
Aqui, usando convolução, é considerada a soma e o produto dos números de 1 a 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
Neste exemplo, o gerador co1 é construído, dividindo sequencialmente o número original por graus 10. Em seguida, os quocientes são selecionados até o zero aparecer. Depois disso, a lista resultante de quocientes é exibida pela função de tomar o restante da divisão por 10. O resultado é uma lista de dígitos do número. A lista é resumida, ele calculou o máximo, mínimo e produto.
Conclusões
A abordagem proposta é bastante viável e pode ser aplicada com sucesso para resolver tarefas diárias de um programador de VBA em um estilo funcional. Por que somos piores que os javistas?
Faça o download de exemplos
aquiBoa sorte !!!