Back-End

2 abr, 2012

JavaScript funcional com CoffeeScript e Node

Publicidade

O CoffeeScript é uma linguagem relativamente nova que se apresenta como uma alternativa atraente para os desenvolvedores cansados das deficiências do JavaScript. Com o CoffeeScript, os desenvolvedores podem fazer sua codificação em uma linguagem leve e intuitiva que parece um híbrido do Ruby e Python. O CoffeeScript se compila ao JavaScript para aplicativos da web compatíveis com o navegador e também funciona perfeitamente com o Node.js para aplicativos do lado do servidor. Centralizado neste artigo está um terceiro benefício de usar o CoffeeScript, que é a sua manipulação do lado funcional do JavaScript. A sintaxe limpa e moderna do CoffeeScript abre as portas do universo da programação funcional latente nas Bibliotecas do JavaScript.

Assim como a JavaScript, a programação funcional é excepcionalmente útil e historicamente impopular. Considerando que a JavaScript foi julgada como sendo uma linguagem de brincadeira, a programação funcional desenvolveu uma reputação de ser excessivamente complexa. Porém, como a demanda por aplicativos altamente simultâneos aumentou, a necessidade de uma alternativa aos estilos de programação imperativa em estado de mudança também aumentou. A programação funcional, como se vê, não é tão desnecessariamente complexa, já que ela é uma ferramenta elegante para a codificação da complexidade inerente em certos tipos de aplicativos.

Neste artigo, vamos explorar o script funcional no CoffeeScript e Node com a ajuda de uma biblioteca JavaScript chamada Underscore. Quando combinadas, estas três tecnologias criam uma pilha eficiente para usar o JavaScript para desenvolver aplicativos do lado do servidor e baseados em navegador que impulsionam a programação funcional.

Observe que este artigo se baseia em minhas apresentações anteriores para JavaScript para Desenvolvedores Java e Node.js para desenvolvedores Java. Presumo que o seu ambiente de desenvolvimento inclua o Node.js e que você esteja familiarizado com a programação básica em Node.

Configurar o CoffeeScript e Node

Se o Node.js já for parte do seu ambiente de desenvolvimento, será possível usar o seu gerenciador de pacotes (NPM) para instalar o CoffeeScript. O comando a seguir instrui o NPM para instalar o pacote de maneira global:

INSERT:CONTENT:ENDgt; npm install -g coffee-script

A maior parte de seu tempo no CoffeeScript será gasto com a escrita de programas, salvando-os para um arquivo .coffee e compilando os resultados para o JavaScript. A sintaxe do CoffeeScript próxima o suficiente da sintaxe do JavaScript, que deve ser familiar para a maioria dos desenvolvedores, por exemplo, o CoffeeScript na Listagem 1, se parece muito com o JavaScript, mas sem a confusão habitual de parênteses e pontos e vírgulas:

INSERT:CONTENT:ENDgt; coffee -bpe "console.log 'hello coffee'"   
console.log('hello coffee');

Lista 1. CoffeeScript Típico

O comando coffee é um atalho para algumas tarefas de gerenciamento. Ele compila arquivos CoffeeScript em JavaScript, executa arquivos CoffeeScript e ainda atua como um ambiente interativo ou REPL (semelhante ao código do Ruby: irb).

Aqui, eu coloco o meu script em um arquivo:

console.log "hello coffee"

Em seguida, compilo (ou converto) aquele arquivo para JavaScript:

INSERT:CONTENT:ENDgt; coffee -c hello.coffee

O resultado é um arquivo chamado hello.js. Como o JavaScript resultante é válido para o Node, posso executá-lo rapidamente em meu ambiente do Node:

INSERT:CONTENT:ENDgt; node hello.js 
hello coffee!

Lista 2. JavaScript no Node

Como alternativa, eu poderia usar o comando coffee para executar o arquivo .coffee, como na Listagem 3:

INSERT:CONTENT:ENDgt; coffee hello.coffee 
hello coffee!

Lista 3. CoffeeScript no Node

Cuidado com o watchr

A comunidade de software livre tem produzido uma série de utilitários de arquivos úteis do watcher que fazem tarefas como executar testes, compilar código e assim por diante. Eles geralmente trabalham por meio da linha de comando e são muito leves. Vamos configurar uma ferramenta watcher para acompanhar todos os arquivos .coffee em nosso ambiente de desenvolvimento e compilá-los para .js depois de serem salvos.

O utilitário de minha preferência para este propósito é o watchr, que está em uma biblioteca Ruby. Para usar o watchr, são necessários um Ruby e um RubyGems instalados em seu ambiente de desenvolvimento. Uma vez configurado, é possível executar o seguinte comando para instalar o watchr como uma biblioteca global Ruby (incluindo o utilitário correspondente):

INSERT:CONTENT:ENDgt; gem install watchr

Em watchr, expressões regulares são usadas para definir os arquivos a serem vistos e o que deve acontecer com eles. O comando a seguir configura o watchr para compilar todos os arquivos .coffee localizados em um diretório src :

watch('src\/.*\.coffee') {|match| system "coffee --compile --output js/ src/"}

Observe que o comando coffee , neste caso, colocará os arquivos .js resultantes em um diretório js.

Eu posso disparar isso em uma janela do terminal, assim:

INSERT:CONTENT:ENDgt; watchr project.watchr

Agora, sempre que faço uma alteração em qualquer arquivo .coffee em meu diretório src , o watchr garantirá que um novo arquivo .js seja criado e colocado em meu diretório js.

CoffeeScript a 10.000 pés

O CoffeeScript apresenta muitos recursos valiosos que o tornam muito mais fácil de se trabalhar do que o JavaScript. Em um nível alto, o CoffeeScript elimina a necessidade de chaves, pontos-e-vírgula, as palavras-chave var e function . Na verdade, uma das minhas características favoritas do CoffeeScript é a sua definição de função , mostrada na Listagem 4:

capitalize = (word) -> 
word.charAt(0).toUpperCase() + word.slice 1

console.log capitalize "andy" //prints Andy

Lista 4. As funções do CoffeeScript são fáceis!

Tenho declarada aqui uma função simples para colocar em letra maiúscula a primeira letra de uma palavra em CoffeeScript. No CoffeeScript, a sintaxe da definição da função segue uma seta. O corpo também é delimitado pelo espaçamento; e é por isso que não há chaves no CoffeeScript. Observe também a ausência de parênteses. O word.slice 1 do CoffeeScript simplesmente compila para o código do JavaScript: word.slice(1). Da mesma forma, observe que o corpo da função é delimitado por espaço: é todo o código recuado abaixo da linha de definição de função. O console.log não recuado abaixo, o que significa que a definição do método está completa. (Esses dois recursos acompanham o CoffeeScript por meio do Ruby e Python, respectivamente.)

Se você estiver tentando imaginar, a função JavaScript correspondente seria algo parecido com a Listagem 5:

var capitalize = function(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
};

console.log(capitalize("andy"));

Lista 5. Até mesmo uma única linha de código em JavaScript é gritante.

Variables

O CoffeeScript coloca automaticamente um -ian var de JavaScript na frente de qualquer variável que você definir. Como resultado, você nunca precisa se lembrar do var quando estiver fazendo codificação em CoffeeScript. (A palavra-chave var é opcional em JavaScript. Sem ela, a sua variável se torna global, o que é quase sempre ruim.)

O CoffeeScript também permite definir valores padrão para parâmetros, conforme mostrado na Listagem 6:

greeting = (recipient = "world") -> 
"Hello #{recipient}"

console.log greeting "Andy" //prints Hello Andy
console.log greeting() //prints Hello world

Lista 6. Valores de parâmetro padrão!

A Listagem 7 mostra como este valor de parâmetro padrão é tratado no JavaScript correspondente:

var greeting;

greeting = function(recipient) {
if (recipient == null) recipient = "world";
return "Hello " + recipient;
};

Lista 7. JavaScript Gritante.

Condicionais

O CoffeeScript manipula condicionais introduzindo palavras-chave, como e, ou, e a não, conforme mostrado na Listagem 8:

capitalize = (word) -> 
if word? and typeof(word) is 'string'
word.charAt(0).toUpperCase() + word.slice 1
else
word

console.log capitalize "andy" //prints Andy
console.log capitalize null //prints null
console.log capitalize 2 //prints 2
console.log capitalize "betty" //prints Betty

Lista 8. As condicionais do CoffeeScript.

Na Listagem 8, eu usei o operador ? para testar a existência. Antes de tentar colocar letras maiúsculas em uma palavra, esse script garantiria que o parâmetro word não fosse null e que ele fosse uma string principal da plataforma Java. É muito bacana que o CoffeeScript permite que você use is no lugar de==.

Definição de classe para programação funcional

O JavaScript não suporta classes diretamente, mas sim, é uma linguagem orientada a protótipo. Para aqueles de nós que estão imersos na programação orientada a objeto, isso pode ser confuso — queremos nossas classes! Para nos deixar satisfeitos, o CoffeeScript fornece uma sintaxe de classe , que produz uma série de funções definidas dentro de funções quando compiladas em JavaScript padrão.

Na Listagem 9, eu uso a palavra-chave de classe para definir uma classe chamada Message:

class Message
constructor: (@to, @from, @message) ->

asJSON: ->
JSON.stringify({to: @to, from: @from, message: @message})

mess = new Message "Andy", "Joe", "Go to the party!"
console.log mess.asJSON()

Lista 9. Opa! O CoffeeScript possui classes.

Na Listagem 9, usei a palavra-chave constructor para definir um construtor. Em seguida, defini um método (asJSON) digitando um nome seguido por uma função.

CoffeeScript e Node

Como ele compila para JavaScript, o CoffeeScript é um ajuste natural para a programação em Node e pode ser especialmente útil para tornar o código Node, que já é limpo, ainda mais puro. O CoffeeScript é particularmente adepto a suavizar muitos dos retornos de chamada do Node, como pode ser visto em uma simples comparação de código. Na Listagem 10, defino um aplicativo simples da web do Node usando o JavaScript puro:

var express = require('express');

var app = express.createServer(express.logger());

app.put('/', function(req, res) {
res.send(JSON.stringify({ status: "success" }));
});

var port = process.env.PORT || 3000;

app.listen(port, function() {
console.log("Listening on " + port);
});

Lista 10. Um aplicativo da web Node.js em JavaScript.

Reescrevendo o mesmo aplicativo da web no CoffeeScript, elimine o ruído sintático dos retornos de chamada do Node, conforme mostra a Listagem 11:

express = require 'express'

app = express.createServer express.logger()

app.put '/', (req, res) ->
res.send JSON.stringify { status: "success" }

port = process.env.PORT or 3000

app.listen port, ->
console.log "Listening on " + port

Lista 11. O CoffeeScript simplifica o Node.js.

Na Listagem 11, o operador ou foi incluído no lugar do JavaScript ||. Também achei que o uso de uma seta para indicar a função anônima em app.listen seria mais fácil de digitar do que function().

Se você emitir um coffee -c neste arquivo, verá que o CoffeeScript produz quase o JavaScript exato definido na Listagem 10. O JavaScript 100% válido do CoffeeScript funciona com qualquer biblioteca do JavaScript.

Coleções funcionais com o Underscore

Anunciado como uma faixa de utilitário funcional para a programação em JavaScript, o Underscore.js é uma biblioteca de funções para facilitar o desenvolvimento em JavaScript. Entre outras coisas, o Underscore oferece uma série de funções orientadas à coleção, cada uma delas perfeitamente adequada à sua tarefa específica.

Por exemplo, suponha que você precisava encontrar todos os números ímpares em uma coleção de números, digamos de 0 a 10, apenas. Enquanto você poderia cortá-los; usar o CoffeeScript e o Underscore juntos evitaria a digitação e, provavelmente, alguns erros. Na Listagem 12, eu forneço o algoritmo básico, enquanto que Underscore fornece a função de agregação, neste caso filter:

_ = require 'underscore'

numbers = _.range(10)

odds = _(numbers).filter (x) ->
x % 2 isnt 0

console.log odds

Lista 12. Função de filtro do Underscore.

Primeiro, como o _ (ou seja, underscore) é um nome de variável válido, eu o configurei para fazer referência à biblioteca do Underscore. Em seguida, juntei uma função anônima à função filter que executa testes para números ímpares. Observe que eu usei a palavra-chave isnt do CoffeeScript ao invés daquela do JavaScript !==. Em seguida, usei a função range para especificar que desejava classificar os números de 0 a 9. Como alternativa, eu poderia ter fornecido um contador de etapas para a minha faixa (ou seja, contando por dois) e iniciado a partir de qualquer número.

A função filter retorna um array que é uma versão filtrada do que quer que tenha sido passado para ele, que neste caso foi [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Portanto, executar o código na Listagem 12 renderia [ 1, 3, 5, 7, 9 ].

A função map é outra das minhas favoritas para aplicar às coleções em JavaScript, como mostrado na Listagem 13:

oneUp = _(numbers).map (x) ->
x + 1

console.log oneUp

Lista 13. Função de mapa do Underscore.

Aqui, a saída seria [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]. Basicamente, o Underscore incrementa cada valor em minha faixa de numbers um por vez, por isso, não é preciso iteragir manualmente para atingir o número inteiro.

Se você precisa testar os aspectos de uma coleção, o Underscore torna essa tarefa tão simples! Basta criar a função como aquela na Listagem 14, que testa a uniformidade:

even = (x) ->
x % 2 is 0

console.log _(numbers).all(even)
console.log _(numbers).any(even)

Lista 14. Função uniforme do Underscore.

Uma vez definida a minha função even , eu posso anexá-la facilmente às funções do Underscore como all e any. Nesse caso, all se aplica à minha função even para cada valor no intervalo numbers . Ela retorna, então, variáveis booleanas indicando que all os valores foram pares (falso). De forma semelhante, a função any retornaria variáveis booleanas se any fosse par (verdadeiro).

O que você acharia se não precisasse aplicar qualquer uma destas funções a uma coleção de valores, mas precisasse fazer mais alguma coisa? Sem problemas! Alavanque a função do Underscore: each . each funciona como um iterador fácil (o que significa que ele lida com a lógica de loop nos cenários, passando a função especificada em cada iteração). Esta função deve ser familiar se você já trabalhou com Ruby ou Groovy.

_.each numbers, (x) ->
console.log(x)

Lista 15. Cada função do Underscore.

a Listagem 15, a função each obtém uma coleção (meu intervalo de numbers ) e uma função a ser aplicada para cada valor no array iterado. Neste caso, eu usei each para imprimir o valor da iteração corrente para console. Eu poderia facilmente ter feito algo como persistir dados a um banco de dados, retornar resultados para um usuário, e assim por diante.

Conclusão

O CoffeeScript infunde um frescor e uma facilidade na programação JavaScript que deve fazê-la se sentir como uma segunda natureza, especialmente para qualquer pessoa familiarizada com Ruby ou Python. Neste artigo, mostrei como o CoffeeScript faz empréstimos de cada uma destas linguagens para tornar o código de estilo JavaScript mais fácil de ser lido e muito mais rápido de ser produzido. Combinar o CoffeeScript, Node e o Underscore, conforme demonstrado, resulta em uma pilha de desenvolvimento incrivelmente leve e divertida para os cenários básicos de programação funcional. Com o tempo e a prática, é possível estender facilmente o que foi aprendido aqui para aplicativos de negócios mais complexos que dependem de web dinâmica e interações remotas.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

  • Participe da comunidade do developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

***

Sobre o autor: Andrew Glover é desenvolvedor, autor, palestrante e empreendedor com uma paixão por desenvolvimento direcionado por comportamento, integração contínua e desenvolvimento de software Agile. Ele é o fundador da easyb , estrutura Behavior-Driven Development (BDD) e é o coautor de três livros: Continuous Integration, Groovy in Action, e Java Testing Patterns. Você pode acompanhá-lo em seu blog do Alex e seguindo-o no Twitter.