Seções iMasters
Desenvolvimento + Linguagens

A sua linguagem de programação consegue fazer isso?

Texto original de Joel Spolsky, disponível em  http://www.joelonsoftware.com/items/2006/08/01.html

?

Um dia, você está olhando para o seu código,
e você nota dois grandes blocos que parecem quase a mesma coisa. Na verdade,
eles são exatamente a mesma coisa, exceto que um deles se refere A
“Spaghetti” e um bloco se refere a “Chocolate Moose.”

// A trivial example:

alert("I'd like some Spaghetti!");
alert("I'd like some Chocolate Moose!");

Esses exemplos são do JavaScript, mas mesmo se você
não saiba JavaScript, você deve ser capaz de acompanhar.

O código repetido parece errado, claro, então você
cria uma função:

   function SwedishChef( food )
{
alert("I'd like some " + food + "!");
}

SwedishChef("Spaghetti");
SwedishChef("Chocolate Moose");

Ok, é um exemplo trivial, mas você pode
imaginar um exemplo mais substancial. Esse código é melhor por muitas razões,
todas aquelas que você já ouviu inúmeras vezes. Manutenção, Legibilidade,
Abstração = ótimo!

Agora você observa outros dois blocos de código que
parecem quase iguais, exceto que um deles continua chamando a função denominada
BoomBoom e a outra continua chamando a função denominada PutInPot. Fora isso, o
código é praticamente o mesmo:

    alert("get the lobster");
PutInPot("lobster");
PutInPot("water");

alert("get the chicken");
BoomBoom("chicken");
BoomBoom("coconut");

Agora você precisa de um modo para passar um argumento
para a função que é, ele mesmo, uma função. Essa é uma capacidade importante,
porque aumenta as chances de você ser capaz de achar um código comum que pode
ser escondido em uma função.  

    function Cook( i1, i2, f )
{
alert("get the " + i1);
f(i1);
f(i2);
}

Cook( "lobster", "water", PutInPot );
Cook( "chicken", "coconut", BoomBoom );

Veja! Estamos passando uma função como um argumento.

A sua linguagem consegue fazer isso?

Espere… digamos que você ainda não definiu as
funções PutInPot ou BoomBoom. Não seria legal se você pudesse escrevê-las em
linhas ao invés de declará-las em algum outro lugar?

    Cook( "lobster", 
"water",
function(x) { alert("pot " + x); } );
Cook( "chicken",
"coconut",
function(x) { alert("boom " + x); } );

Caramba, isso vem a calhar. Note que estou criando
uma função aqui do nada, sem nem me preocupar em nomeá-la, apenas pegando-a
pelas orelhas e a colando dentro de uma função.

Assim que você começar a pensar em funções anônimas
como argumentos, você pode notar um código todo espalhado por aí, que, digamos,
faz algo a cada elemento de um array.

    var a = [1,2,3];

for (i=0; i<a.length; i++)
{
a[i] = a[i] * 2;
}

for (i=0; i<a.length; i++)
{
alert(a[i]);
}

Fazer algo a cada elemento de um array é bastante
comum, e você pode escrever uma função que faça isso por você:

    function map(fn, a)
{
for (i = 0; i < a.length; i++)
{
a[i] = fn(a[i]);
}
}

Agora você pode reescrever o código acima como:

map( function(x){return x*2;}, a );
map( alert, a );

Outra coisa comum dos arrays é combinar todos os
valores do array de alguma maneira.

    function sum(a)
{
var s = 0;
for (i = 0; i < a.length; i++)
s += a[i];
return s;
}

function join(a)
{
var s = "";
for (i = 0; i < a.length; i++)
s += a[i];
return s;
}

alert(sum([1,2,3]));
alert(join(["a","b","c"]));

sum e join
parecem tão semelhantes que você pode querer abstrair sua essência em
uma função genérica que combina os elementos de um array em um valor único:

    function reduce(fn, a, init)
{
var s = init;
for (i = 0; i < a.length; i++)
s = fn( s, a[i] );
return s;
}

function sum(a)
{
return reduce( function(a, b){ return a + b; },
a, 0 );
}

function join(a)
{
return reduce( function(a, b){ return a + b; },
a, "" );
}

Muitas linguagens antigas simplesmente não permitiam
fazer esse tipo de coisa. Outras linguagens permitem que você faça isso, mas é
difícil (por exemplo, C tem ponteiros de funções, mas você tem que declarar e
definir a função em outro lugar). Linguagens de programação orientadas a objeto
não estão completamente convencidas de que você tem permissão para fazer qualquer
coisas com funções.

O Java pede que você crie um objeto completo com um único
método chamado functor se você quiser tratar uma função como um objeto de
primeira classe. Combine isso com o fato de muitas linguagens OO quererem que
você crie um arquivo completo para cada classe, e ele rapidamente vai ficar bastante
desengonçado. Se sua linguagem de programação pede que você use
functors, você não está tendo acesso a todos os benefícios do ambiente moderno
de programação. Veja se você consegue seu dinheiro de volta.

Quão benéfico
realmente é escrever cada pedacinho de funções que não fazem nada além de
iterar através de um array fazendo alguma coisa em cada elemento?

Bem, vamos voltar à função map. Quando você
precisa fazer algo em cada elemento de um array de cada vez, a verdade é que provavelmente não importa a ordem em que você faz isso. Você pode percorrer um
array de frente pra trás, de trás pra frente, e ainda ter o mesmo resultado,
certo? Na verdade, se você tiver duas CPUs em mãos, você poderia escrever algum
código e ter cada CPU fazendo metade dos elementos, e assim a map se
torna duas vezes mais rápida.

Ou, talvez, apenas hipoteticamente, você tenha
centenas de milhares de servidores em vários data centers em todo o mundo, e você
tem um array realmente grande, contendo, digamos, mais uma vez
hipoteticamente, todo o conteúdo da internet. Agora você pode executar a map em
mil computadores, cada um deles atacando uma parte do problema.

Então, por exemplo, escrever um código bem rápido
para procurar por todo o conteúdo da internet é tão simples como chamar a
função map com um string de busca básico como argumento.

O que é realmente
interessante aqui é que assim que você pensa em map e em reduce como funções que
todo mundo pode usar, e eles as usam, você somente precisa ter um super gênio
para escrever o código para executar map e reduce em um array de computadores
massivo e paralelo, e o código antigo que costumava funcionar perfeitamente
quando você apenas para executar um loop ainda funciona, só que um zilhão de
vezes mais rápido, o que significa que ele pode usado para resolver problemas
grandes em um instante. 

Deixe-me repetir isso. Ao abstrair o conceito de
looping, você pode implementar o looping da maneira que você quiser,
incluindo implementá-lo de uma maneira que escale perfeitamente se você
tiver hardware extra. 

Agora você entende algo que escrevi há algum tempo, quando reclamei sobre estudantes de CS que nunca aprenderam nada
além de Java
(inglês):

Sem entender programação funcional, você não pode
inventar MapReduce, o algoritmo que torna o
Google tão massivamente  escalável. Os termos Map e Reduce vêm do Lisp e
da programação funcional. MapReduce é, em retrospecto, óbvio para qualquer um
que lembre de suas aulas de programação em que programas puramente funcionais não
têm efeitos colaterais, sendo assim trivialmente paralelizáveis. Só o fato de
que o Google inventou o MapReduce, e não a Microsoft, diz algo sobre a
Microsoft ainda estar correndo atrás do prejuízo, tentando fazer seus recursos
básicos funcionarem, enquanto o Google já migrou para o próximo problema:
construir o Skynet ^H^H^H^H^H^H, maior supercomputador do mundo massivamente
paralelo. Acredito que a Microsoft não entende completamente quão longe eles
estão de um lugar ao sol…

Ok. Espero que você esteja convencido, agora, de que
linguagens de programação com funções de primeira classe permitem que você
encontre mais oportunidades para abstração, o que significa que seu código será
menor, mais coeso, mais reutilizável e mais escalável. Muitas aplicações do
Google usam MapReduce e todas elas se beneficiam sempre que alguém otimiza ou
conserta os bugs.

Agora vou ficar um pouco sentimental, e discutir que os
ambientes de programação mais produtivos são aqueles que deixam o seu trabalho
em diferentes níveis de abstração. A porcaria do FORTRAN nem deixava você
escrever funções. O C tinha ponteiros de função, mas eles eram feios, não
anônimos, e tinham que ser implementados em um lugar que não fosse o que você
estivesse usando. O Java fez você usar functors, que são ainda mais feios.
Como Steve Yegge destacou, Java é o  Reino dos Substantivos.

Correção: A última vez que usei FORTRAN foi há 27
anos. Aparentemente agora ele tem funções. Eu devia estar pensando em GW-BASIC.


Mensagem do anunciante:

Torne-se um Parceiro de Software Intel®. Filie-se ao Intel® Developer Zone. Intel®Developer Zone

Comente também

13 Comentários

Junio Silva

O C# também tem recursos funcionais como delegates e expressões lambda.

    Tomamais

    Sim, mas é um recurso adicional. No C#, não é claro você passar uma função como argumento. O JavaScript nasceu com isso.

    Junio Silva

    Os recursos funcionais estão presente desde a terceira versão em 2008.
    E a sintaxe é bem parecida com a do Javascript, pode-se fazer assim por exemplo (obs, há várias formas):

    class Delegates
    {
    string calcular(Func função, int numero1, int numero2)
    {
    return função(numero1, numero2).ToString();
    }

    public void UsandoDelegates()
    {
    string a = calcular((x, y) => x – y, 2, 3);
    }
    }

Rafael

E o PHP? Ele também faz tudo isso! :)

thiago

php tbm, mas gostei disso vou dar uma estuda :D

William Bruno

Ótimo texto !

Realmente, javascript é uma linguagem linda !

Flavio Ferreira

Python tambéM

Faustino Faria

Embora as novas linguagens tenham ótimos recursos e grande vantagem sobre as antigas, em 1990 a gente fazia isso com Clipper 5.0 usando Codeblocks :)
…aliás…
…muita gente ainda continua fazendo com o [x]Harbour.

Concordo em grande parte que o javascript é uma excelente linguagem e realmente essas funções anonimas são a mão na roda , mas não devemos desconsiderar as outras grandes linguagens como Java , PHP e C# . O Java ainda está introduzindo essas novas formas de programar no jdk7 e o php introduziu agora , mas terá melhoras na versão 6 (quando sair……)e como vi nos outros comentários o C# tem duas funções parecidas ou iguais. O que estou querendo dizer que como o mundo muda, as linguagens de programação estão mudando tambem. Mas isso é normal.Se nós temos que nos atualizar,a cada momento porque não as linguagens não podem?.

Douglas Miranda

Realmente, javascript é surpreendente. Eu conhecia boa parte dessas dicas, porém cada dia aprendo algo novo de javascript e vejo que o limite é a imaginação :)

Galindo

não tive paciencia de ler

Carlos

“O Java fez você usar functors, que são ainda mais feios.
Como Steve Yegge destacou, Java é o Reino dos Substantivos.”

Por que functors são feios? Isso é muito subjetivo… Ok, precisa escrever mais código em Java do que em Javascript, mas isso não torna Javascript necessariamente mais legível. Pelo contrário, Javascript não é type-safe, o que pode gerar inúmeros bugs difíceis de depurar.

P.S: o imasters só copia e modifica artigos de outros sites?

Qual a sua opinião?