Lambda functions, closures, partials functions, curryng, são conceitos e técnicas existentes há muito tempo na área de ciência da computação, principalmente no estudo do paradigma de desenvolvimento funcional. Muitos destes conceitos vem sendo adotado por linguagens a priori orientada a objetos, no PHP não é diferente, já a algum tempo podemos implementar várias destas técnicas.
Desde a versão 5.3 o PHP tem suporte a lambda functions e também tem suporte a First-Class Functions e High-Order Functions. First-Class Functions permite tratar uma função como qualquer outro valor, atribuí-la a uma variável, passá-la como parâmetro, etc. High-Order Functions significa que uma função pode receber outra função como argumento e/ou retornar uma função como resultado. Algumas funções desse tipo que já existem no core do PHP e que são bastante utilizadas são por exemplo: array_map, array_reduce, funções de ordenação: usort, uksort.
Isso nos permite implementar algumas técnicas de programação funcional e criar códigos mais expressivos, fáceis de ler e dar manutenção. Também possibilita uma maior reutilização de códigos, agilizando o desenvolvimento e deixando o projeto mais consistente. Vamos ver como funciona e como podemos implementar essas técnicas.
Lambda functions/Funções anônimas
Funções anônimas simplesmente permite a criação de uma função sem nome específico, podendo ser atribuída a uma variável, passada como argumento para uma função ou como retorno de uma função.
No primeiro exemplo criamos uma função anônima que recebe dois
parâmetros e atribuímos a variável $sum
, assim chamamos como uma
função normalmente. No segundo exemplo criamos uma função nomeada
soma
que recebe um callback(função) como primeiro parâmetro e mais
dois números como parâmetros, ao chamar essa função passamos como
callback a função anônima $sum
, dentro dessa função executamos o
callback passando os dois números restantes.
Closures
Uma Closure é semelhante a uma função anônima, a diferença esta no
fato de que uma Closure tem acesso a variáveis do escopo externo.
Para isso o PHP possui a palavra-chave use
, possibilitando passar
qualquer variável do escopo externo para dentro da função.
No primeiro exemplo criamos uma função anônima comum tentando
acessar uma variável do escopo externo, ao tentar executar a
função temos um “PHP Notice” dizendo que a variável $name
dentro
da função não existe. Na segunda função usamos o use
para passar
a varável $name
para dentro da função, assim temos acesso a ela ao
chamarmos a função.
Lambda functions/Closures são instancia da classe Closure, esta classe possui alguns métodos.
Closure::call
Vincula a Closure temporariamente a um objeto/escopo e a chama com qualquer parâmetro passado.
Na linha 21 e 22 estamos chamando a Closure $addAge
vinculando ao
escopo do objeto $person1
e $person2
respectivamente, e passando o
parâmetro que ela necessita, então ela esta executando como se fosse
um método interno aos respectivos objetos.
Closure::bindTo/Closure::bind
Os métodos bindTo/bind retorna uma nova Closure com um objeto e um escopo vinculado, não é temporário, assim, se o objeto vinculado for alterado, o resultado da chamada da nova Closure poderá ter seu valor alterado também. Clojure::bind é uma versão static de Closure::bindTo.
Nesse exemplo estamos usando os métodos bindTo e bind(static) para
vincular definitivo a Closure $addAge
nos objetos $person1
e $person2
respectivamente. Esse método retorna uma nova Closure que estamos
atribuindo a $closurePerson1
e $closurePerson2
. Aqui podemos perceber
que se alterarmos a propriedade $age
dos objetos e chamarmos
$closurePerson1
e $closurePerson2
novamente o resultado retornado é
diferente.
Closure::fromCallable
Closure::fromCallable permite converter um Callable(função) em uma Closure. Para isso precisamos passar a função que queremos converter como parâmetro para esse método. Essa função verifica se o callable esta no escopo atual, se for do escopo externo vai lançar um erro.
Partial functions
Utilizando Closures podemos criar algo chamando partials functions. Partials functions são funções que pegam uma função existente e reduzem a quantidade de parâmetros ligando um ou mais de seus parâmetros a um valor específico, retornando uma outra função com esses parâmetros já definidos. As partials functions permite dividir em funções mais específicas e compartilhar a funcionalidade central de uma função mais ampla. O objetivo é poder fixar os argumentos necessários e retornar outra função com esses valores definidos, e assim, na chamada da nova função, definir apenas os parâmetros restantes.
No exemplo acima, praticamente criamos um “constrututor” de funções de
multiplicação. Criamos uma função makeMultiply
que recebe um número
como parâmetro ($x) e retorna uma outra função, mas já fixando um
valor da multiplicação como o parâmetro recebido, assim podemos
construir outras funções mais específicas utilizando esta como base.
Por exemplo, se quisermos uma função que multiplica qualquer número
por 2 (linha 11) ou por 5 (linha 12), podemos usa-la para isso.
Podemos criar uma função generalista que retorna uma partial function de qualquer outra função, assim podemos reutilizá-la sempre que necessário.
Assim, possibilita criarmos funções generalista e ir criando as funções mais específicas quando necessário, o mesmo exemplo de cima.
Currying
Utilizando Closures também podemos fazer currying de funções. Currying é uma técnica utilizada para transformar uma função que recebe múltiplos parâmetros em uma cadeia de funções, cada uma lidando com apenas um parâmetro da função inicial. Após feito o currying, uma função rebe o primeiro parâmetro e devolve uma função que aceita o segundo e assim por diante.
No primeiro exemplo utilizando uma função comum recebendo três parâmetros, e retornando a soma dos mesmos. No segundo código, utilizando currying, separamos a lógica em três funções, cada uma trabalhando com apenas um parâmetro, assim podemos chamá-la como uma sequência de funções. Embora se pareça com partials function, currying e partials functions são conceitos e técnicas diferentes.
Conclusão
Acabamos de ver algumas técnicas utilizando lambda functions/Closure
que auxiliam na resolução de problemas, reusabilidade de código e
construção de um código mais limpo e expressivo.
Cada caso deve ser avaliado e estudado qual a melhor técnica para
resolver determinado problema, os recursos estão disponíveis,
bastando apenas a aplicação da resolução.
Referências:
https://wiki.php.net/rfc/closurefromcallable
https://wiki.php.net/rfc/closure_apply