Dev (Back & Front)ARTIGO

Aproveitando lambdas e encerramentos do PHP 5.3

Nos últimos anos, tem havido muito movimento na questão das
linguagens de programação.
Os desenvolvedores estudam linguagens como Ruby, Groovy e Clojure –
não só para aumentar sua competitividade, mas também para expandir seu
raciocínio.

Dois dos recursos mais animadores dessas linguagens agora
estão disponíveis com lambdas e encerramentos do PHP 5.3.
E há outra linguagem que a maioria dos desenvolvedores PHP já usa e que
tem excelente implementação de lambdas e encerramentos: JavaScript.

Com o acréscimo de lambdas e encerramentos, o PHP fica compatível
com requisitos de uma linguagem de programação funcional.
Especificamente, as linguagens de programação funcionais devem fornecer
funções de alta ordem e primeira classe, definidas neste artigo.

Nos
últimos dois anos, o interesse em programação funcional disparou. Por
quê? Simples: a lei de Moore, que afirma que a capacidade de computação
dobra a cada dois anos, não é mais verdade.
Ela não é mais verdadeira porque, enquanto a capacidade de computação
aumenta em processador de um só núcleo, os fabricantes de computadores
agora podem aumentar a capacidade criando processadores de vários
núcleos.

É por isso que apareceram processadores dual, quad e hexa-core.
Fazer processadores com vários núcleos é relativamente fácil para os
fabricantes de computadores, mas as linguagens stateful e baseadas em
pilhas não se beneficiam necessariamente de processadores adicionais. As
linguagens de programação funcionais sim.

O que são lambdas e encerramentos e por que um desenvolvedor PHP
deve ficar animado com a disponibilidade deles? Este artigo descreve
como e onde usar lambdas e encerramentos, expõe algumas peculiaridades
do comportamento dos encerramentos e apresenta alguns idiomas de código
que podem ser usado para aproveitar lambdas e encerramentos.

Lambdas: rápidos e simples

O termo lambda vem de cálculo Lambda, um sistema formal de
definição de função, aplicativo e recursão criado por Alonzo Church nos
anos 1930.
Algo interessante sobre o Sr. Church é que seus colegas de trabalho
na Universidade Stanford incluíam Alan Turing da famosa máquina Turing.
As ideias que esses dois homens formularam criaram a base das linguagens
de programação modernas.

Mas não deixe que a complexidade do cálculo o desanime de usar
lambdas: Eles não passam de funções anônimas. A seguinte definição de
função, por exemplo, é um lambda:

$horse = function() {return "with no name";};
print $horse();

Repare que o terminador ponto-e-vírgula (;) é necessário, visto que a função está sendo designada a uma variável. Antes do PHP 5.3, era preciso criar uma função padrão:

function horse() {return "Secretariat";}
print horse();

“Grande coisa!”, você vai dizer. “Os dois exemplos têm o mesmo número de linhas e, além disso a função sem nome basicamente tinha um nome na variável $horse .” Na verdade, $horse é uma referência à função, não o nome dela.

Mas a verdadeira vantagem de lambdas é ao criar funções durante a execução, quando não será preciso nenhuma referência à variável. Por exemplo:

$a=array('PA'=>"Pennsylvania",'VA'=>"Virginia",'TX'=>"Texas");
print_r(array_filter($a,
function($v){return $v==="Virginia";}
)
);

Nesse caso, a função lambda permanece sem nome: É criada de forma sequencial como parâmetro da função array_filter. Criar lambdas durante a execução como parâmetros de funções e métodos é o caso de uso principal deles.

Repare que o termo retorno em geral é usado para descrever funções que são passadas como parâmetros para outras funções. Os retornos costumam ser usados em arquiteturas de manipulação de evento, onde o código a ser chamado é designado posteriormente.

Na programação funcional, os lambdas são chamados de funções de primeira classe
porque podem ser:

  • Passados como parâmetros.
  • Criados e usados no tempo de execução.
  • Retornados a partir de uma função.
  • Designados a variáveis.

Na verdade, os lambdas estão disponíveis para os desenvolvedores de PHP desde a V4.0 com o uso de create_function:

$horse = create_function('', 'return "with no name";');
print $horse();

Mas a sintaxe de create_function é deselegante. Por um lado, o código fica em uma grande sequência de caracteres entre aspas. Por isso, não espere ajuda do seu editor de PHP, e não se esqueça de escapar da sequência de códigos. Além disso, o código de create_function é compilado no tempo de execução e a função resultante não pode ser colocada em cache.

Asserção de lambdas

Aqui está um exemplo simples que usa um lambda – e eu o usarei por todo o restante deste artigo. Este é o requisito do aplicativo: quando leio artigos sobre PHP que contêm fragmentos de código, em geral eles mostram resultados com um echo ou print seguido por um comentário que descreve a saída esperada. Veja, por exemplo, o código a seguir: 

function horse() {return "Secretariat";}
print horse(); // should list: Secretariat

O leitor precisa executar o código e verificar visualmente a suposição implícita. Quando leio:

print horse(); // should list: Secretariat

Acho que vou ficar estrábico ao oscilar entre o código e o comentário. Para tornar esses fragmentos de código de artigo mais legíveis, uso asserts de PHP. Por exemplo:

assert(horse() == "Secretariat");

A configuração das asserções de PHP permite especificar uma função a ser chamada sempre que assert for chamada. Naturalmente, usou um lambda:

assert_options(ASSERT_CALLBACK, 
function ($script, $line) {
echo "assert failed file:$script line:$line ";
}
);

Quando uma asserção falha, a saída obtida é o nome do script e o número da linha:

assert failed file:/var/www/closure_behavior.php line:13 PHP Warning: 
assert(): Assertion failed in /var/www/closure_behavior.php on line 13

Uma alternativa a usar lambda na asserção seria:

function assertCallback($script, $line) {
echo "assert failed file:$script line:$line ";}
);
assert_options(ASSERT_CALLBACK, assertCallback);

“E por que a versão com lambda é melhor?”, talvez alguém pergunte.
Vou explicar. Tenha em mente que a versão não lambda define uma função
que é usada apenas em um local.

Entenda que o código é lido muito mais
do que escrito, de modo que é importante simplificá-lo. Sempre que é
definida uma função com nome, seu leitor tenta colocar o nome da função
em seu cache mental, o que complica desnecessariamente o entendimento
que o leitor tem do programa.

Se uma função é para ser uma operação de
uso único, use um lambda e simplifique o código.

No restante deste artigo, sempre que aparecer assert, tenha certeza de que a asserção é verdadeira.

Início com encerramentos

Se os lambdas não são nada mais que funções sem nome, os encerramentos são pouco mais do que lambdas com contexto. Ou seja, um encerramento
é uma função anônima que, quando criada, anexa a si mesma o valor das
variáveis do escopo do código que criou a função.

As variáveis são
ligadas em um encerramento com a palavra-chave use. Por exemplo, se existem as seguintes variáveis:

$states=array('PA'=>"Pennsylvania",'VA'=>"Virginia",'TX'=>"Texas");
$st = 'Texas';

É possível ligar a variável $st ao encerramento com a palavra-chave use no exemplo a seguir:

$tx = array_filter($states, function($v) use($st) {return $v==$st;});

Daí, assert é bem-sucedido:

assert(array_keys($tx) == array('TX'));

Como ilustra o exemplo acima, um encerramento é uma função que é “encerrado em” variáveis especificadas. O exemplo acima é um bom caso de uso para encerramentos: Ele usa uma das 18 funções de array PHP padrão que pegam uma função como parâmetro (como array_walk, usort e array_app).

Os encerramentos permitem expandir o uso dessas funções de utilitário. Antes do encerramento, a implementação da função passada foi limitada ao contexto de seus argumentos da função. Na verdade, isso não é completamente correto, porque poderiam ter sido usados globais, mas faz parte da sabedoria convencional dizer que o uso de globais é prática ruim de programação.

O array_filter de PHP é um exemplo do que é conhecido na programação funcional como função de alta ordem. Uma função de alta ordem (na matemática e nas ciências da computação) é aquela que pega uma função como entrada ou que envia uma função para a saída.

Depois de estar confortável com lambdas e encerramentos, talvez você se pegue reduzindo código e criando códigos mais legíveis e reutilizáveis escrevendo suas próprias funções de alta ordem.

Um encerramento é, na verdade, um novo tipo de dado implementado internamente com a nova classe Closure. Esta é outra maneira de encará-lo: se um objeto for dados com métodos que operam nesses dados, um encerramento é uma função com dados ligados a essa função.

Comportamento do encerramento

É fácil entender a sintaxe da palavra-chave use do
encerramento.
Mas é preciso mexer um pouco com encerramentos antes de começar a
entender o comportamento das variáveis passadas para eles por meio da
palavra-chave use.

Tenho certeza que você entende muito bem escopos de variáveis: o
código bem-escrito define variáveis apenas no escopo para o qual é
necessário. Com isso em mente, espera-se que a asserção no fim do
seguinte código falhe:

function getNameFunc() {
$string = 'Denoncourt';
return function() use ($string) { return $string; };
}
$name = getNameFunc();
$name();
assert ($name() == 'Denoncourt');

Mas a asserção é bem-sucedida – embora $string esteja fora do escopo. Isso se dá porque as variáveis especificadas em uma palavra-chave use de encerramento têm escopo definido lexicamente.

Vou explicar: as variáveis PHP em geral têm escopo definido dinamicamente, o que significa que as variáveis são colocadas em uma pilha. Essas variáveis saltam da pilha quando a função que as contém é concluída. As variáveis com escopo definido lexicamente se referem ao ambiente local onde são definidas.

Em vez de serem colocadas em uma pilha, as variáveis especificadas com a palavra-chave use são armazenadas em um armazenamento estático.

É um recurso poderoso do encerramento que – independentemente de onde seja definido – pode ser chamado posteriormente, de qualquer lugar, e executado com suas variáveis específicas de usuário ainda em um escopo válido.

Cuidado, porém, que a implementação de PHP de escopo lexical copia as variáveis especificadas na palavra-chave use para a área de armazenamento estático. Ao usar objetos grandes, pode-se reduzir o armazenamento passando uma referência para a variável, assim:

$string = 'Denoncourt';
$changeName = function() use(&$string) {$string = 'Smith';};
$changeName();
assert ($string == 'Smith');

Como o exemplo acima também mostra, passar variáveis de uso por
referência é um modo de permitir que o encerramento modifique os
valores.

“Espere aí”, você vai dizer. “Por que não passar simplesmente a variável que passou na opção
use do encerramento com argumento da função?”. É aí que
se revela o poder das variáveis com escopo definido lexicamente.

Com
argumentos de função, os parâmetros passados devem estar no escopo da
pilha. Mas com variáveis de uso, o encerramento basicamente lembra o
contexto da variável. Considere o exemplo da Listagem 1, onde um
encerramento é definido em uma classe e, depois, chamado após a variável
do objeto sair do escopo.

Listagem 1. Chamando um encerramento após a variável sair do escopo

class Person {
var $first;
var $last;
public function sayHello() {
$that = $this;
return function() use ($that) {
return $that->first.' '.$that->last;
};
}
}
function stackFunc1() {
$me = new Person();
$me->first = 'Don';
$me->last = 'Denoncourt';
return $me->sayHello();
}
$sayHi1 = stackFunc1();
function stackFunc2() {
$me = new Person();
$me->first = 'Sue';
$me->last = 'Swartzbaugh';
return $me->sayHello();
}
$sayHi2 = stackFunc2();
assert($sayHi1() == 'Don Denoncourt');
assert($sayHi2() == 'Sue Swartzbaugh');

Repare que a implementação do método Person::sayHello cria um alias para a variável especial $this e o passa para a definição de encerramento. Isso se dá porque o encerramento é implementado internamente com a classe Closure e essa classe tem seu próprio contexto $this.

De fato, se for passado $this na palavra-chave use, haverá um erro similar a este: 

PHP Fatal error:  Cannot use $this as lexical variable

Os casos de uso para os métodos de classes que retornam funções são
limitados apenas pela sua criatividade. Para obter um exemplo de uso
criativo de encerramentos, verifique nossa classe do utilitário Pawel
Turlejski, que fornece métodos de array semelhantes a Groovy.

Com o uso de encerramentos, é possível até mesmo codificar suas
classes para permitir a criação dinâmica de métodos. A Listagem 2
fornece um exemplo.

Listagem 2. Codificação com criação de método dinâmico

class Person {
var $first;
public function __call($method, $args) {
return call_user_func_array( $this->$method, $args);
}
}
$me = new Person();
$me->first = 'Don';
$me->sayGoodbye = function() use ($me) {
return 'Bye '.$me->first;
};
assert($me->sayGoodbye() == 'Bye Don');

Novamente, mesmo que o objeto saia do escopo, o encerramento poderá ser chamado com sucesso.
Por exemplo, a execução de encerramento acima funciona mesmo que tenha sido removida a referência de objeto $me com o método unset de PHP:

$bye = $me->sayGoodbye;
unset($me);
assert($bye() == 'Bye Don');

Os argumentos passados para funções de ordem mais alta, e depois passados como argumentos para uma palavra-chave use de encerramento também retêm seus valores:

$greeting = function($greet) {
return function($name) use($greet) {
return $greet.' '.$name;
};
};
$hi=$greeting('Yoh');
assert($hi('Don') == "Yoh Don");
assert($hi('Sue') == "Yoh Sue");

Chamando classes

Antes, mencionei que os encerramentos eram implementados internamente com uma classe chamada Closure. A propósito, essa classe talvez mude em versões posteriores de PHP, por isso não a use diretamente no desenvolvimento.

Mas há um recurso dos itens internos de implementação da classe Closure que pode ser explorado: é o uso do novo método __invoke. O método mágico __invoke faz com a uma classe possa ser chamada:

class Invoker {
public function __invoke() {return __METHOD__;}
}
$obj = new Invoker;
assert ($obj() == 'Invoker::__invoke');

Quando uma classe que contém uma implementação de __invoke é instanciada, pode-se chamá-la especificando parênteses ( () ) após o nome do objeto. Esse tipo de objeto é conhecido como functor
ou objeto de função.

Conclusão

Seus aplicativos podem se beneficiar do uso de lambdas e
encerramentos. Substituindo definições de função formais por funções
anônimas, o código é simplificado.

Desenvolvendo funções de utilitário e
métodos como funções de ordem mais alta, seu aplicativo fica mais
robusto. Entendendo encerramentos e escopo lexical, é possível começar a
perceber as oportunidades para novos padrões e idiomas de
desenvolvimento. E aprendendo mais sobre programação funcional, seus
horizontes como desenvolvedor são ampliados.

Deve-se usar PHP como linguagem de programação funcional? Em geral,
não. PHP surgiu como uma linguagem simples e concisa para o
desenvolvimento de aplicativos da Web.

Desenvolver aplicativos da Web
baseados em PHP usando semântica puramente funcional vai de encontro à
intenção original da linguagem. Seja como for, a sintaxe de PHP para
lambdas e encerramentos não é muito elegante – especialmente se comparada
a linguagens como Ruby, Groovy e Clojure.

Mas há cenários em que
conceitos de programação funcional podem ser aplicados em aplicativos
PHP, resultando em códigos mais simples, concisos e fáceis de entender.

Download

Descrição Nome Tamanho Método de download
Sample PHP scripts for this article os-php-lambda-Closure_example.zip 4KB HTTP

Recursos

Aprender

Discutir

***

artigo publicado originalmente no developerWorks Brasil, por Don Denoncourt

Don Denoncourt é consultor, instrutor, mentor e
autor freelance especializado em tecnologia Java, Groovy, Grails e PHP.
É possível entrar em contato com ele através do e-mail
dondenoncourt@gmail.com.

é o portal de tecnologia da IBM para profissionais de TI de todo o mundo. Colabore, aprenda a criar aplicativos inovadores e compreenda tecnologias avançadas. Acesse mais artigos e tutoriais em www.ibm.com/developerworks/br

Ver perfil