Eu sou um fã de CoffeeScript e gostaria de mostrar alguns exemplos que me fazem lembrar que nunca mais vou escrever nenhuma linha de javascript puro.
Primeiramente eu não gosto de ficar repetindo as coisas e coffee é tudo de bom pra fazer isso. No javascript tem um monte de parênteses e chaves arbitrários porém acabam sendo mais chatos e deixam o código um pouco mais denso.
Coffeescript é uma linguagem que idealiza várias ideias que me atraem.
- Simplicidade
- Confidência
- Minimalismo
Vou citar caso a caso as coisas que mais gosto. Todas elas são complementares e ajudam a manter a facilidade. Coffeescript é javascript. Logo tudo que estou fazendo é usar o transpilador para gerar o código final em js.
Se você curtir, vai copiando e colando os exemplos no j2.coffee.
Função
Em javascript teríamos:
maisUm = function(n) { n + 1 }
Em coffeescript temos:
funções
Então não precisa de function nem {}:
maisUm = (n) -> n + 1
Indentação inteligente
As chaves não são substituídas por um código indentado. Isso quer dizer que você vai usar espaços ao invés de chaves para todos lados.
maisUm = (n) -> n + 1
Se a função for complexa a identação vai expandindo e fica fácil de entender o contexto.
parOuImpar = (n) ->
if n % 2 is 0
'par'
else
'impar'
Também é possível fazer inline com then.
parOuImpar = (n) -> if n % 2 is 0 then 'par' else 'impar'
Essas opções inline são muito interessantes para explorar pois elas podem ser combinadas e gerar um código bem compacto e com bastante significado.
Por exemplo, vamos combinar a função de par ou impar para iterar sobre um array:
classificarParOuImpar = (array) ->
for n in array
if n % 2 is 0
'par'
else
'impar'
Inline
A função acima, irá retornar um array de strings ‘par’ ou ‘impar’. Se fosse escrever inline seria:
classificarParOuImpar = (array) -> if n % 2 is 0 then 'par' else 'impar' for n in array
Multiplas atribuições
As múltiplas atribuições já existem no javascript e não há nada de novo nisso. No entando o coffeescript tem a sua própria maneira de fazer.
Isso é muito interessante o quão simples torna as atribuções. Exemplo de atribuição sequencial:
a = 1 b = 2
Então em uma linha atribuindo múltiplas variáveis ao mesmo tempo:
[a,b] = [1,2]
Multiplas atribuições de hashes
As atribuições também funcionam para hashes.
Esse é um dos meus preferidos, principalmente pela simplicidade de extrair atributos. Imagina o seguinte código:
peso = pessoa.peso altura = pessoa.altura idade = pessoa.idade
Escrevendo em uma linha:
{peso, altura, idade} = pessoa
Agora, se você estiver super empolgado após usar as variáveis acima e quiser retornar um hash modificado com as medidas normalmente iria fazer:
{peso: peso, altura: altura, idade: idade}
Mas você pode simplificar ainda mais usando a seguinte sintaxe:
{peso, altura, idade}
Eu acho interessante a ideia de ter apenas uma maneira de se escrever um código mas gosto dessas variações que simplificam e minimizam a linguagem em si.
Acredito que as declarações como {a: a, b: b} são repetitivas e
desnecessárias: Entendendo que {a,b} irá gerar exatamente isso, consigo
manter um código mais limpo e menos verboso. Eu sinceramente gosto muito disso.
Parâmetros se transformando em atributo de acesso
Em um exemplo simples poderia escrever:
class Pessoa
constructor: (nome) ->
@nome = nome
Eu posso simplesmente usar @ no parâmetro e funciona da mesma maneira,
criando o atributo interno do objeto.
class Pessoa constructor: (@nome) ->
E você não está limitado a usar apenas no constructor, pois pode usar onde
bem entender este tipo de método e pode ser extremamente útil.
class Pessoa
constructor: (@nome) ->
indiceMassaCorporal: (@altura, @peso) ->
@imc = @peso / (@altura * @altura)
Desta maneira eliminamos uma série de códigos repetitivos e verbosos.
||= atribuições condicionais
Uma possível melhoria para fazer no algorítmo acima, ainda seria evitar recalcular o imc se já foi calculado, cacheando o imc, ou recalculando:
class Pessoa
constructor: (@nome) ->
indiceMassaCorporal: (@altura, @peso) ->
@imc ||= @peso / (@altura * @altura)
O exemplo é tosco mas apenas seguindo a linha de usar cache para o cálculo e evitar reprocessamento.
Condicionais?
Estilo narrador do polishop:
- Transtornado com propriedades não definidas?
- Recebe um
undefined is not a functiona cada tentativa de rodar um novo código?
Seus problemas acabaram! O super poder das condicionais no coffeescript é muito simples e fácil de usar! E se você comprar agora ainda pode levar inteiramente grátis as opções de trabalhar com condicionais encadeadas!
Veja com seus próprios olhos essa beleza em funcionamento:
if pessoa? pessoa.indiceMassaCorporal(190,112)
Também pode ser condicional inline.
pessoa?.indiceMassaCorporal(190,112)
E encadeado. Por exemplo, verificar se existe a função indiceMassaCorporal
antes de executá-la:
imc = pessoa?.indiceMassaCorporal?(190,112)
No fim das contas detalhes como estes acabam poupando algumas linhas e o código fica extremamente fácil de entender.
Além disso dá pra usar uma série de funções anônimas inline e outros detalhes que realmente fazem o coffeescript ter seu brilho próprio.
Entre eles eu destaco: Tudo está sendo retornado no coffee. Então qualquer expressão é inline e pode ser re-utilizada.
Cada for é um map e está retornando o array da expressão interna. Então, você pode declarar um código como:
imcsDeVariasPessoas = pessoa.imc for pessoa in pessoas
Detalhes difíceis e cruéis
Nem tudo são flores e você vai precisar pisar em ovos no coffee também. Uma das
maiores enrascadas do coffeescript está no @ e a falta de sabedoria na hora
de usar ele.
Um bom conselho que posso dar é: mantenha-se sempre olhando o código javascript gerado pois você está compilando javascript e no final não pode gerar incompatibilidades da linguagem.
O @ é o this. do javascript. Então, ao se referir a uma propriedade você
naturalmente usa @. Supomos o exemplo:
euclidean = (p1, p2) ->
[a, b] = [p1.x - p2.x, p1.y - p2.y]
Math.sqrt Math.pow(a, 2) + Math.pow(b, 2)
class Pessoa
distancia: (outro) -> euclidean(@,outro)
amigosProximos: (raioEsperado = 50) ->
proximos = []
@relacionamentos.filter (pessoa) ->
if @distancia(pessoa) < raioEsperado
proximos.push pessoa.nome
proximos
Esse é o clássico algorítmo que não funciona e você fica puto da cara com isso.
Então é necessário entender o código gerado e você vai perceber que o
@distancia não existe naquele contexto. Nesse caso, não existe pois uma
função interna que faz o código do filter funcionar. Veja o código javascript gerado:
var Pessoa, eu, euclidean;
euclidean = function(p1, p2) {
var a, b, ref;
ref = [p1.x - p2.x, p1.y - p2.y], a = ref[0], b = ref[1];
return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
};
Pessoa = (function() {
function Pessoa() {}
Pessoa.prototype.distancia = function(outro) {
return euclidean(this, outro);
};
Pessoa.prototype.amigosProximos = function(raioEsperado) {
var proximos;
if (raioEsperado == null) {
raioEsperado = 50;
}
proximos = [];
this.relacionamentos.filter(function(pessoa) {
if (this.distancia(pessoa) < raioEsperado) {
return proximos.push(pessoa.nome);
}
});
return proximos;
};
return Pessoa;
})();
Criando a referência self
class Pessoa
distancia: (outro) -> euclidean(@,outro)
amigosProximos: (raioEsperado = 50) ->
proximos = []
self = @
@relacionamentos.filter (pessoa) ->
if self.distancia(pessoa) < raioEsperado
proximos.push pessoa.nome
proximos
Neste caso, o mais legal é usar a solução clássica de usar list comprehensions que é uma maneira simples e padrão do que manter-se usando uma variável self.
Mas segue de exemplo com self acima e abaixo com list comprehensions.
euclidean = (p1, p2) ->
[a, b] = [p1.x - p2.x, p1.y - p2.y]
Math.sqrt Math.pow(a, 2) + Math.pow(b, 2)
class Pessoa
distancia: (outro) -> euclidean(@,outro)
amigosProximos: (raioEsperado = 50) ->
pessoa for pessoa in @relacionamentos when @distancia(pessoa) < raioEsperado
Então observando o exemplo final e o código gerado, escrevemos 7 linhas de coffeescript que geraram mais de 30 em javascript.
Observe o código final:
var Pessoa, euclidean;
euclidean = function(p1, p2) {
var a, b, ref;
ref = [p1.x - p2.x, p1.y - p2.y], a = ref[0], b = ref[1];
return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); };
Pessoa = (function() {
function Pessoa() {}
Pessoa.prototype.distancia = function(outro) {
return euclidean(this, outro);
};
Pessoa.prototype.amigosProximos = function(raioEsperado) {
var i, len, pessoa, ref, results;
if (raioEsperado == null) {
raioEsperado = 50;
}
ref = this.relacionamentos;
results = [];
for (i = 0, len = ref.length; i < len; i++) {
pessoa = ref[i];
if (self.distancia(pessoa) < raioEsperado) {
results.push(pessoa);
}
}
return results;
};
return Pessoa;
})();
E aí gostou? <a href=http://js2.coffee/#coffee/try:euclidean%20%3D%20(p1%2C%20p2)%20-%3E%0A%20%20%5Ba%2C%20b%5D%20%3D%20%5Bp1.x%20-%20p2.x%2C%20p1.y%20-%20p2.y%5D%0A%20%20Math.sqrt%20Math.pow(a%2C%202)%20%2B%20Math.pow(b%2C%202)%0Aclass%20Pessoa%0A%20%20distancia%3A%20(outro)%20-%3E%20euclidean(%40%2Coutro)%0A%20%20amigosProximos%3A%20(raioEsperado%20%3D%2050)%20-%3E%0A%20%20%20%20pessoa.nome%20for%20pessoa%20in%20%40relacionamentos%20when%20%40distancia(pessoa)%20%3C%20raioEsperado%0Aeu%20%3D%20new%20Pessoa()%0Aeu.x%20%3D%2010%0Aeu.y%20%3D%2020%0Aeu.relacionamentos%20%3D%20%5B%0A%20%20%7B%20nome%3A%20%22maria%22%2C%20x%3A%205%2C%20y%3A%2010%20%20%20%20%7D%0A%20%20%7B%20nome%3A%20%22joao%22%2C%20%20x%3A%2020%2C%20y%3A%2040%20%20%207D%0A%20%20%7B%20nome%3A%20%22z%C3%A9zin%22%2C%20x%3A%20200%2C%20y%3A%20500%207D%0A%5D%0Aconsole.log%20eu.amigosProximos()”>Tenta rodar na prática</a> esse exemplo e não deixe de comentar!
Quais são suas principais dificuldades com coffeescript?
Confesso que apanhei e também sofri muito pois foi a primeira vez que trabalhei com uma linguagem que obriga a seguir uma indentação, mas no final têm sido ótimo, valendo muito a pena.
Sabemos que o javascript pode sofrer alterações drásticas, e a comunidade vai manter a compatibilidade.
Então de certa forma é até mais confortável usar coffeescript do que manter adaptando e readaptando o código para rodar na especificação mais recente e retro-compatibilizado.
Pense nisso! o/