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.

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:

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%20%7D%0A%20%20%7B%20nome%3A%20%22z%C3%A9zin%22%2C%20x%3A%20200%2C%20y%3A%20500%20%7D%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/


Share → Twitter Facebook Linkedin


Hello there, my name is Jônatas Davi Paganini and this is my personal blog.
I'm developer advocate at Timescale and I also have a few open source projects on github.

Check my talks or connect with me via linkedin / twitter / github / instagram / facebook / strava / meetup.