Interfaces funcionais ... em VBA

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

Nome do métodoParâmetrosResultado
isGen-Retorna True se o objeto for um gerador.
isCont-Retorna True se o objeto for um contêiner.
getCont-Para um contêiner retorna uma matriz local, para um gerador retorna Vazio
getNext-Retorna o seguinte valor
hasNext-Retorna True se o seguinte valor estiver presente
InitiniVal como variante, lambda como string = "", emptyC como booleano = falsoiniVal - valor inicial;
lambda - função anônima para gerador
emptyC - quando definido como True, um contêiner vazio é criado
Tomen como inteiroRetorna um contêiner contendo n valores consecutivos obtidos do objeto de origem
Filtrolambda como stringRetorna o objeto obtido filtrando o original de acordo com a função lambda sem nome
Mapalambda como stringRetorna o objeto obtido mapeando o original de acordo com a função lambda sem nome
Reduziracc Como variante, lambda como sequência,Retorna o resultado da convolução do objeto atual com o valor inicial da bateria acc e a função de dobragem especificada pelo parâmetro lambda
takeWhilen Como Inteiro,
lambda As String
Retorna um contêiner contendo n (ou menos) valores consecutivos que satisfazem o predicado especificado pela função lambda sem nome
dropWhilen Como Inteiro,
lambda As String
Retorna um contêiner contendo n (ou menos) valores consecutivos obtidos do original após ignorar valores que atendem ao predicado especificado por lambda.
zipiter Como aIter,
n Como Inteiro = 10
Ele aceita um contêiner ou gerador e retorna um contêiner contendo pares de valores - do contêiner base e do contêiner. O tamanho padrão do resultado é dez.
zipWithiter Como aIter,
lambda como corda,
n Como Inteiro = 10
Leva um contêiner e uma função anônima de dois argumentos. Retorna um contêiner que contém os resultados da aplicação da função especificada a pares sucessivos - um valor do contêiner base e outro do contêiner de parâmetros.
Tamanho-Para um contêiner, retorna o número de elementos
summa-Soma dos valores do contêiner
produção-Produto dos valores do contêiner
máximo-O valor máximo no contêiner
mínimo-O valor mínimo no contêiner

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 aqui

Boa sorte !!!

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


All Articles