Recentemente, resolvi testar o editor Atom… Mas isso eu já falei em outro post.

O que eu vim falar agora é sobre CoffeeScript, e a forma como eu tenho programado recentemente envolvendo Javascript.

Estando no mundo Rails, é bem comum a gente acabar usando as ferramentas que o Rails nos disponibiliza. Embora seja uma boa idéia no começo, há uma tendência a ficarmos meio “engessados” numa única tecnologia. Então, resolvi começar um projeto meio ousado de fazer uma UI inteira em HTML + CoffeeScript, sem a intervenção do Rails, e por hora, nem sequer com backend. Talvez eu fale sobre isso num post futuro, mas especificamente agora quero falar de Coffee.

Eu nunca gostei de CoffeeScript, na verdade, por causa da idéia de “indentation-based”. Outra coisa do Coffee é que ele basicamente não faz milagres – tudo no fim das contas é Javascript, e é essa a parte difícil e chata de entender.

O principal problema de CoffeeScript, na verdade, é uma de suas principais vantagens: ele abstrai class, this.variable, e outras coisas, mas justamente aí que as coisas começam a ficar confusas – o escopo de Javascript não é o mesmo escopo de linguagens orientadas a objeto, logo o this aponta para coisas diferentes dependendo de qual parte da função você está…. por exemplo:

class SomeClass
  someMethod: ->
    @variable # Isso aqui não é a mesma coisa

    [1, 2, 3].forEach (value) ->
      @variable # que isso aqui

Coffee oferece um work-around: sempre que você não quiser mudar o this, você pode usar uma seta grossa ao invés de uma seta fina:

class SomeClass
  someMethod: ->
    @variable # Isso aqui agora é a mesma coisa

    [1, 2, 3].forEach (value) =>
      @variable # que isso aqui

O problema é lembrar dessas coisas… e não fazer confusão também. Outra coisa confusa do CoffeeScript é, novamente, as regras envolvendo semânticas do Javascript. Talvez por estarmos mais acostumados, certas coisas no JS não nos assustam – já em coffee, certas coisas que gostaríamos que funcionassem do jeito certo não funcionam. Por exemplo:

# Cria uma lista de multiplicadores, por 1, 2 e 3:
# Como o escopo de variáveis é o de JS, o "i" vai para
# fora do for, e consequentemente, a função criada pegará
# apenas o último valor da lista... ou seja, 4
multipliers = for i in [2, 3, 4]
  (param) -> param * i

multipliers[0](10) # 40?
multipliers[1](10) # 40?
multipliers[2](10) # 40

# Forma certa de fazer:
multipliers = for i in [2, 3, 4] then do ->
  z = i # Bind para uma variável que só existe aqui dentro...
  (param) -> param * z

multipliers[0](10) # 20
multipliers[1](10) # 30
multipliers[2](10) # 40

Sim, é confuso. Na verdade, é BEM confuso, e muito fácil de errar. Isso acontece principalmente porque as semânticas mudam – em JS, escreveríamos o código de uma maneira que seria óbvio que a variável está fora do for…

Coffee oferece umas vantagens absurdas para códigos Javascript. A principal delas é uma forma meio limitada de pattern match, mas que ajuda bastante a decompor javascript objects e arrays. O “splat” de coffee também auxilia a criar funções com número arbitrário de parâmetros, e também parâmetros com valor padrão…

sum1 = (a, b=20) -> a + b
sum1(20) # => 30
sum1(20, 1) # => 21

sum2 = (values...) ->
  acc = 0
  for v in values then acc += v
  acc
sum2(1, 2, 3) # => 6

sum3 = ({p1, p2}) -> p1 + p2
sum3(p1: 10, p2: 20) # => 30

[ten, nine] = [10, 9]
ten # => 10
nine # => 9

{a, b} = {a: 10, b: 20}
a # => 10
b # => 20

arr = [
  1
  2
]
arr # => [1, 2]

map = 
  one: 1
  two: 2
  three: 3
map.one # => 1

E é aqui que as coisas começam a ficar confusas – em Coffee, há muitos helpers para muitas coisas. O último exemplo, acima, mostra como o “map” pode ser definido sem usar a sintaxe de javascript objects. Mas o que acontece com:

map = 
  a: 10
  c = 30
  b: 20

Ele dará um erro de sintaxe: “unexpected newline”, ou seja, uma nova linh não esperada na linha que está o a: 10. Porém, isso aqui é correto:

class SomeClass
  m1: "Foo"
  m2 = "Algo"
  m3: "Bar"

E para onde vai o m2? Fica sendo uma variável privada, dentro da classe. Mas repare que eu falei classe, não instância. Significa basicamente isso aqui:

class SomeClass
  a = 10
  getA: -> a # Returns A
  setA: (value) -> a = value

c1 = new SomeClass()
c2 = new SomeClass()

c1.setA(30)
c2.getA() # => 30 !!!!!
new SomeClass().getA() # => 30 !!!!

Além do mais, os escopos são diferentes: Definir uma variável usando @var = “foo” dentro da classe fará essa variável pertencer aos objetos daquela classe. Definir uma variável com var: “foo” na definição da classe fará ela pertencer ao protótipo da classe, o que é bem bizarro… mas na prática, não muda muito o escopo da linguagem:

class SomeClass
  a: 10
  getA: -> @a # Returns A
  setA: (value) -> @a = value

c1 = new SomeClass()
c2 = new SomeClass()

c1.setA(30)
c2.getA() # => 10

O que eu tenho mais dificuldade com Coffee é saber quando a indentação funciona e quando não. Por exemplo:

# Isso aqui é igual...
findPerson.then (p) ->
  p.name
.then (name) ->
  name.toUpperCase()

# a isso
findPerson
.then (p) ->
  p.name
.then (name) ->
  name.toUpperCase()

# e a isso
findPerson
  .then (p) ->
    p.name
.then (name) ->
  name.toUpperCase()

# Mas, obviamente, diferente disso:
findPerson
.then (p) ->
  p.name
  .then (name) ->
    name.toUpperCase()

Nos primeiros 3 casos, é chamado o método then do findPerson, com um parâmetro que é uma função. O que isso retornar, será chamado o then. No último caso, é chamado o método then do findPerson, com um parâmetro que é uma função. Essa função tem como corpo p.name.then(….). Obviamente, nesse caso, é fácil de ver o problema. O problemático é que, devido à natureza assíncrona do Javascript, esses métodos costumam expandir para várias linhas… e é difícil entender o que vem dentro de quê (especialmente pensando que podemos usar indentação para criar arrays e javascript objects)…

No geral, usar CoffeeScript é uma vitória frente a Javascript. Porém, é possível montar aplicativos complexos apenas com Coffee?

Bom, essa resposta depende muito de quem se pergunta. Eu, particularmente, sinto falta ainda de construções mais imponentes como HashMaps, Sets, e classes mais avançadas (em Javascript por exemplo há agora a classe Map e a classe Set, mas elas são montadas de tal forma que apenas strings e números funcionam bem como chaves). Logo, a minha resposta seria: ainda não.

Além disso, há alguns suportes faltando no Coffee: métodos de classe (static methods em Java), um private de verdade, o comportamento estranho do this, o suporte a javascript attributes que o Coffee não suporta (getters e setters é tudo o que temos, em pleno 2016), falta uma maneira de usar mix-in ou algo semelhante (ok, dá pra criar uns mecanismos mas… sério?), e, acho que o principal de tudo, é muito, muito fácil sobrescrever variáveis definidas um escopo acima. Em Javascript, quando usamos var, estamos sempre definindo uma nova variável. Se uma variável com o mesmo nome já existia, ela é entendida como outra variável – ou seja, shadowed. Em Coffee, não dá pra fazer isso – tudo o que temos é usar var a = 10;, ou seja, usando backticks para fazer um fallback para Javascript. Mas de novo – sério?

Além do mais, com a popularização do ES6, é provável que o Coffee fique obsoleto muito rápido….


2 Comments

Thiago · 2016-01-21 at 23:41

Pois é Maurício. Quando eu conheci o Coffee, pensei: nossa, que negócio legal! Mas com o passar do tempo, vi que não era tudo isso e que na verdade acabava sendo mais confuso que o próprio JavaScript já é. Acabei praticamente não usando Coffee nos meus projetos. Acredito que o JavaScript vai incorporar aos poucos algumas ideias de Coffee, como as Arrow Functions já presentes no ES6 (achei beeem legal). Ah… faltou você falar do uso do “let” para limitar escopo. Abraço, mestre!

    Maurício Szabo · 2016-02-16 at 10:42

    Então, quando eu comecei a escrever, o “let” e o “const” estavam ainda em discussão para incorporar o Coffee. Não sei o estado que eles estão hoje…

Comments are closed.