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
- Leia os artigos anteriores desta série de três artigos do developerWorks: “Desenvolvimento Java 2.0: JavaScript para desenvolvedores Java” (Andrew Glover, abril de 2011) e “Node.js para desenvolvedores Java” (Andrew Glover, novembro de 2011).
- A série de Neal Ford, chamada Pensamento funcional , vai além da sintaxe de programação funcional e dos conceitos subjacentes, com exemplos escritos em Groovy, Scala e na linguagem Java.
- Se você quer saber mais sobre o CoffeeScript, consulte:
- “Your first cup of CoffeeScript, Part 1: Getting started” (Michael Galpin, developerWorks, dezembro de 2011).
- “The Java technology zone technical podcast series: Ryan McGeary” (Andrew Glover, developerWorks, fevereiro de 2011).
- Para obter respostas às suas perguntas sobre o Node, comece com “O que exatamente é o Node.js?” (Michael Abernethy, developerWorks, maio de 2011).
- Desenvolvimento Java em 2.0 é a série do developerWorks de Andrew Glover que explora tecnologias que estão redefinindo o panorama de desenvolvimento Java.
- Navegue na livraria da tecnologia Java para ver livros sobre este e outros tópicos técnicos.
- Zona tecnologia Java do developerWorks: Encontre centenas de artigos sobre quase todos os aspectos da programação Java.
Obter produtos e tecnologias
- A pilha de desenvolvimento de aplicativos demonstrada neste artigo inclui:
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.