Implementando as funções “map”, “reduce” e “filter” do JavaScript no Delphi

Samuel R. O.
6 min readAug 27, 2022

--

“Nada se cria, tudo se copia”- Lavosier? Ou será que ele copiou de alguém?

Métodos Anônimos

Antes de falar sobre o assunto, é bom se tivermos uma revisão sobre os assim chamados “métodos anônimos”. Pois para “copiarmos” essas funções do JavaScript, precisamos entender como eles funcionam na prática, já que na essência, elas utilizam os métodos anônimos genéricos.

Em 2010 foi introduzido um novo recurso chamado de métodos anônimos no Delphi. Basicamente falando, eles são métodos que não possuem uma assinatura definida. Ou seja, simplesmente informamos parâmetros ou retornos (caso seja uma função anônima) na sua implementação. Esse tipo de método pode ser usado entre as linhas de um comando ou até mesmo atribuído à uma variável. Vejamos então como funcionam.

Primeiramente, criamos um tipo que faz referência a um procedimento. Lembre-se, em ciência da computação, quando falamos de referência, estamos dizendo sobre “um endereço no qual o computador olha na memória”. Quando vamos a uma biblioteca, geralmente temos o endereço do livro que queremos ler, ou seja, uma referência. Aqui o tipo é uma referência a um procedimento:

Tipo anônimo

Então, por exemplo, podemos criar uma variável do tipo “TAnonimo” e atribuir um método a ela.

Método anônimo

Finalmente, invocamos o nosso método anônimo, chamando a variável.

Invocando o método anônimo

Esses métodos possuem um ciclo de vida similar às variáveis locais. Ou seja, eles apenas existem dentro do escopo do procedimento. Isso se deve ao fato que por trás das cenas, os métodos anônimos são utilizados com interfaces, que implementam um método chamado “Invoke” no qual chama nosso procedimento. Como são implementados por interfaces por debaixo dos panos, eles possuem um contador de referência. Assim que esse contador de referência atinge o ordinal 0, o programa libera ele da memória e a referência deixa de existir.

Não daria para entrarmos em muitos detalhes, mas o fato é que a partir do Delphi 2009, temos a declaração desses métodos anônimos genéricos na unit SysUtils. Eles vêm dessa forma:

Declaração de métodos/funções anônimas genéricas na unit SysUtils do Delphi

Como pode perceber, além de fazer utilização do recurso de referência, esses tipos também utilizam o recurso de tipo genérico (T);

Um exemplo de fora

Apenas uma nota. Se você já conhece o projeto Horse, vai provavelmente perceber que o método para enviar o Callback da requisição, foi feito utilizando esses tipos anônimos.

Exemplo de um método anônimo em um projeto

O método “map”

Muito bem, agora que já tivemos uma pequena revisão sobre os métodos anônimos, vamos criar uma classe utilitária para “copiar” os métodos “map”, “reduce” e “filter” do JavaScript no Delphi. Se você não conhece esses métodos, eles basicamente servem para fazer operações em arrays.

O método “map” é utilizado para criar um novo array, chamando uma função em cada elemento do antigo array informado. Você pode por exemplo, copiar um array de “animais” usando o método map. Não é comum fazer isso mas veja:

Função “map” em JavaScript
Resultado da função

Podemos modificar o novo array com uma função que bem quisermos para cada elemento no map:

Função “map” em JavaScript
Resultado da função “map”

Vejamos como podemos “copiar” essa função “map” para o Delphi. Fazemos utilizando os recursos disponíveis na linguagem, os métodos anônimos genéricos. Iremos criar uma unit chamada TUtil para implementar nossa função “map” nela.

Implementação da função “map” no Delphi

Observe como podemos criar um novo array com letras maiúsculas, a partir de um outro array de letras minúsculas, utilizando a função “map” da nossa classe.

Utilizando a função “map” no Delphi

Vamos dissecar esse código. Observe bem esse método, principalmente seus parâmetros:

Parâmetros da função “map” no Delphi

O primeiro argumento (Args: TArray<T>) é bem fácil de entender pois ele se refere ao array de que estamos tratando em questão. Ou seja, como estamos trabalhando com um array de letras e queremos transformar todas elas em maiúsculas, criamos um array genérico(T) do tipo String. Esse array é referenciado ao ser passado nesse primeiro argumento Args.

O segundo argumento (F: TFunc<T, T>) é mais difícil de entender se não tivermos a própria função “map” em vista. Saiba no entanto, que este argumento se refere à duas coisas genéricas: A primeira, o T da esquerda, é uma referência ao primeiro parâmetro da função anônima, que representa cada item percorrido do array. O segundo, o T da direita, é uma referência ao resultado desta função anônima.

Complexo de explicar, mas fácil se visualizar. Veja se fica claro para você o que cada argumento desta função “map” representa.

Dissecando a função “map” no Delphi

O método “reduce”

Entendendo como funciona este segundo parâmetro da função “map”(F: TFunc<T, T>). Fica fácil de entender a função “reduce”. Pois, já que o primeiro “T” , se refere a um parâmetro da função anônima, e o segundo “T”, ao resultado desta função, precisamos de mais um “T” para conseguir fazer a função reduce, pois ela precisa de dois parâmetros, a saber, o acumulador e o item do array.

Veja como fica a função “reduce” no JavaScript:

Função “reduce” em JavaScript
Resultado da função “reduce”

Como disse, esse segundo parâmetro vai ser representado por mais um “T” na lista genérica da função anônima, representada pelo segundo parâmetro da nossa função "reduce". Vamos esconder o outro método para não confundir como implementamos ela.

Implementação da função “reduce” em Delphi

Para utilizar é bem simples também. Apesar da verbosidade na escrita (coisa que no JavaScript não acontece), o princípio é o mesmo. Passamos um array com os itens a qual iremos “reduzir” até chegar a um único elemento. No nosso caso, é uma soma de valores.

Utilizando a função “reduce” em Delphi
Resultado da função “reduce”

Da mesma forma como ocorreu com a função “map”, fica mais fácil entendermos esse segundo argumento (F: TFunc<T, T, T>) se visualizarmos .

Dissecando a função “reduce” em Delphi

O método “filter”

No método “filter” um novo array é criado com os elementos que passam em um teste provido em uma função. Dado que provemos um array de palavras com os nomes de algumas frutas, podemos por exemplo, criar um novo array somente com as frutas contendo a letra “b”. Vejamos como isso fica no JavaScript.

Função “filter” em JavaScript
Resultado da função filter

Você acha que para implementar a função “filter” no Delphi precisaríamos de quais parâmetros para integrar a função anônima genérica (F: TFunc<??>)? É difícil responder sem escrever o código primeiro. Dentro desta função haverá uma condição testando se nossa função passada como referência é verdadeira ou não. Portanto, vamos precisar de pelo menos um parâmetro, e um resultado booleano. Temos que fazer da forma F: TFunc<T, Boolean>.

Por que não utilizamos F: TFunc<T, T>? Afinal, um Boolean não é também um genérico? Sim, o problema é que se utilizarmos desta maneira não conseguimos testar a nossa condição dentro da nossa função “filter”.

Veja então como fica essa função “filter” na classe:

Implementando a função “filter”no Delphi

O código if F(Args[I]) then quebraria na linha 28 se utilizássemos F: TFunc<T, T>. Lembre-se que todo Boolean é um genérico, mas nem todo genérico é um Boolean. O compilador não saberia o que fazer.

Para utilizar essa função “filter” no Delphi é bem simples também:

Utilizando o a função “filter” no Delphi

À nível de simplicidade, não irei mais explicar visualmente mas creio que você já tenha entendido como essas funções anônimas genéricas funcionam. Tem muitos detalhes que não sei explicar pois elas podem ser utilizadas com propriedades, atribuídas à variáveis, englobar variáveis etc… Queria apenas demonstrar o que pode ser feito utilizando elas para um melhor aproveitamento do código. Esses são de fato poderosos recursos que a linguagem Pascal no Delphi atualmente possui.

Obrigado pela leitura, espero ter contribuído um pouco a favor de sua curiosidade hoje. Até mais! Mantenha-se curioso!

Inspiração

Esse post foi inspirado nessa fonte: https://dzone.com/articles/fun-with-anonymous-methods-using-higher-order-func

Código no Github

Os exemplos utilizados neste post estão neste repositório.
https://github.com/p-samuel/delphi-anonymus-methods

--

--