Uma visão geral sobre o Elm

Já procurou alguma vez framework Javascript na internet? Já viu quantos frameworks diferentes que o Javascript pode ter? Em vez de perder horas procurando, deixe que o Elm facilite sua busca pelo frontend ideal, da melhor forma.

Quantas vezes você já se viu tentando debugar seu front-end e se deparou com aquele emaranhado de código aninhado? E aquele código complexo que você precisa seguir uma sequência de eventos até encontrar o que deseja? E então, se esbarrou com aquela típica mensagem “undefined is not a function” com um stack-trace nada amigável e difícil de decifrar? Debugar Javascript pode ser (e será) bastante doloroso. Tentando escapar disso? Vamos conhecer o Elm!

O que o Elm não é

Primeiramente vamos entender o que o Elm não é:

Ecossistema Javascript

"Esse tal de Elm é mais uma daquelas linguagens baseadas no Javascript e que possui uma sintaxe mais bonitinha com várias novas features?"

Nada disso, apesar de compilar para Javascript o Elm não é mais um desses supersets que adicionam novas features, muito pelo contrário, Elm possui muito menos features do que o Javascript, e não entenda isso como um ponto negativo, longe disso, o Elm mantém o essencial para a linguagem.

"Se não é um superset, então esse Elm é um framework novo?"

Não, o Elm não é o mais novo revolucionário framework para o Javascript.

"Já que não é um superset e nem um framework, só pode ser uma biblioteca. Acertei?"

Também não. Elm não é Javascript.

Elm

elm-lang logo

Elm é uma linguagem de programação funcional, fortemente tipada que possui estruturas de dados imutáveis. Escrita em Haskell e compilada para HTML, CSS e Javascript com ênfase no desenvolvimento front-end e que vem com a promessa de não possuir erros em tempo de execução.

Ela tem como principal “propaganda” não possuir erros em tempo de execução, ou seja, se compilar, provavelmente você nunca mais encontrará erros na sua aplicação. Isso é possível pois no momento da compilação, o elm-make (compilador do Elm) detecta todos os possíveis problemas que poderiam ocorrer em runtime e nos dá um feedback (bem amigável) para tratá-los e, muitas vezes, também nos diz como tratá-los.

Desempenho

Quando o assunto é performance o Elm não deixa a desejar, estando à frente dos principais frameworks e bibliotecas front-ends do mercado:

Comparação de desempenho http://elm-lang.org/blog/blazing-fast-html-round-two

Atualmente todos os browsers suportam a API do DOM. DOM significa "Document Object Model", é uma maneira de representar e interagir com os dados no HTML. Seu principal problema é a perda de performance quando se trata de uma UI mais dinâmica.

Podemos trabalhar com o DOM utilizando Javascript e bibliotecas como JQuery, elas facilitam a manipulação do DOM mas não resolvem os problemas de performance. Por exemplo, vamos imaginar que temos uma página com um "Infinite scroll", depois de um tempo navegando teremos algumas centenas de divs, aí que entra a parte dolorosa, manipular esse DOM com centenas de divs se torna uma tarefa bastante custosa.

Atualmente, a maioria dos frameworks Javacript implementam o Virtual DOM. Virtual DOM é uma técnica que nos permite melhorar o desempenho do front-end, evitando trabalhar diretamente com o DOM. Ao invés de manipular o DOM diretamente, criamos uma versão mais simples e mais leve dele e trabalhamos com ela. Com essa representação virtual podemos alterar o que quisermos e depois salvar as alterações no DOM real.

A primeira etapa é saber quando atualizar o DOM. Para fazer isso, o Elm compara o estado atual da aplicação com o estado anterior, se houver alguma diferença o algoritmo irá modificar o Virtual DOM refletindo a alteração do estado na interface. Geralmente isso pode ser feito de duas maneiras:

  • 1 — Verifica recursivamente os valores de cada estado a fim de encontrar alguma diferença.
  • 2 — Compara os estados por referência, se a referência da memória onde o estado está alocado for diferente, o mesmo foi alterado.

O Elm funciona da segunda maneira pois ele só possui dados imutáveis, ou seja, se quisermos alterar algo criamos uma cópia dos dados com as alterações necessárias, assim, criando uma referência na memória diferente dos dados anteriores, dispensando uma checagem de cada valor.

A segunda etapa é como aplicar estas alterações, para isso existem algoritmos responsáveis por essas mudanças. Por exemplo Matt-Esch/virtual-dom implementado em JS e temos a implementação do Elm.

"Então eu preciso saber tudo isso pra poder desenvolver front end com Elm?"

Não. Todos esses procedimentos são abstraídos na biblioteca de HTML do Elm. Se quiser se aventurar com o Virtual DOM do Elm, o código também está disponível no Github.

Compilador

Normalmente olhamos os compiladores como inimigos, aquela coisa que demora um tempão lendo nosso código pra no final impedir que ele seja executado e informar o porquê dele não estar rodando, mas geralmente esse porquê não é muito claro. Um dos objetivos do Elm é mudar a forma como você vê o compilador, o compilador do Elm foi desenvolvido para atuar como seu assistente, e não como seu inimigo.

O compilador do Elm não faz apenas encontrar os erros na sua compilação, ele te ajuda a entender o porquê desse problema estar acontecendo, além de te dar dicas que te ajudam a melhorar seu código e torna seu desenvolvimento mais rápido e de certa forma, divertido.

Como funciona o processo de debugar no Javascript?

  • 1 — Você escreve seu código;
  • 2 — Abre o navegador;
  • 3 — Clica em algumas coisas;
  • 4 — Encontra um erro;
  • 5 — Abre o console do seu navegador e encontra, por exemplo:
undefined is not a function | filename.js:30; 
  • 6 — Identifica o arquivo e a linha onde o erro está ocorrendo;
  • 7 — Tenta corrigir o erro;
  • 8 — Repete todo o processo até funcionar;

Agora vamos ver como funciona o processo de debug no Elm:

  • 1 — Escrever o código
  • 2 — Tenta compilar
  • 3 — Encontra um erro, recebe a seguinte mensagem:
  • 4 — Corrige o erro com auxílio as dicas do compilador

Como podemos ver, a diferença que temos ao debugar uma aplicação em Javascript e uma em Elm é grotesca, do lado do Javascript temos uma mensagem de erro que só nos informa o arquivo, a linha e um stack-trace nada amigável. Do lado do Elm temos uma mensagem com a descrição do erro, a linha (que é mostrada exatamente do jeito que foi escrita), cores e além disso, o compilador ainda nos dá uma lista de possíveis formas de corrigir o problema.

Fica clara a diferença da experiência que temos ao desenvolver com Elm, o compilador age como um assistente, te ajudando a escrever o código, ao invés de apenas te mostrar (em runtime) que seu código não está funcionando. Com a ajuda da tipagem estática, o compilador rastreia todo o fluxo de dados da sua aplicação a fim de encontrar possíveis erros e assim garantir que a aplicação vá para produção sem erros em tempo de execução.

Arquitetura Elm

Simples e poderosa, a arquitetura Elm estrutura sua aplicação em 4 partes (Model, View, Update e Main) e define como cada uma deve interagir com a outra. No Elm só existe essa maneira de estruturar aplicações, provendo um código mais modularizado, reutilizável e fácil de manter.

Para desenvolvedores que utilizam React essa arquitetura já é familiar pois grande parte do Redux foi inspirado na arquitetura do Elm.

Como exemplo vamos desenvolver uma aplicação simples que exibe um comentário com botões de reações semelhante aos comentários do Facebook.

Resultado do exemplo

Mais adiante mostrarei como fazer a instalação do Elm em sua máquina, enquanto isso sugiro que acompanhe o exemplo através do editor online Ellie.

Module e Imports

Primeiro vamos iniciar nossa aplicação criando o módulo Main e importando algumas funções do módulo Html que iremos utilizar.

  • 1 — Definimos o nome do módulo e as funções que iremos expor para outros módulos. Primeiro utilizamos a keyword “module” seguido do nome do módulo que deve ser o mesmo nome do arquivo. O “..” indica que vamos expor todas as funções presentes neste módulo.
  • 2 — Importamos as tags div, h3, text, p, button e o tipo Html do módulo Html.
  • 3 — Importamos o atributo “class” do módulo Html.Attributes.
  • 4 — Importamos o evento “onClick” do módulo Html.Events.

Model

A Model representa o modelo dos dados da nossa aplicação, é nela que fica todo o estado/dados da app e cada vez que ele é atualizado pela função update recebemos um novo model contendo o estado transformado.

A Model da nossa aplicação de exemplo ficará assim:

Primeiro criamos um Type Alias da nossa Model que será um Record contendo os campos desejados. Em seguida definimos a constante initialModel que vai representar o estado inicial da nossa Model.

Os Type Alias também funcionam como construtores, por exemplo:

initialModel : Model
initialModel =
Model "Javascript" "Undefined is not a function" 0 0 0 0 0 0

View

A View é definida por uma ou mais funções puras. Essas funções recebem a model como input e retornam o HTML que representa o estado atual.

  • 1 — Assinatura da função: primeiro vem o nome da função (view) seguido de ":", depois informamos quais os tipos dos inputs da função (no caso só temos um input do tipo Model) seguido de "->". Por último o tipo de retorno da função (Html Msg).
  • 2 — Definição da função: primeiro temos o nome da função (view), depois os parâmetros (model) e por último o corpo da função.
  • 3 — Tag div: div é uma função que recebe como primeiro parâmetro uma lista de atributos html (class, id, src, href, …), no segundo parâmetro uma lista de outras tags html (p, h1, div, li, …) e retorna outro HTML. A assinatura da função div é a seguinte:
div : List (Attribute msg) -> (Html msg) -> Html msg

À primeira vista a sintaxe pode assustar um pouco mas depois de entender o básico da linguagem veremos que é mais simples do que parece.

Update

O Update é o único lugar de toda a aplicação onde o estado pode ser transformado, é uma função que recebe uma mensagem (Msg) e a model (Model) para então atualizar a model de acordo com a ação.

  • 1 — Msg: é um Union Type, utilizamos para criar novos tipos de dados e seus possíveis valores, por exemplo, temos o tipo boleano: "Bool = True | False". No nosso caso definimos o tipo Msg para informar as possíveis ações que podem ocorrer dentro da nossa aplicação.
  • 2 — Pattern Matching: é uma técnica bastante utilizada em linguagens funcionais para executar uma determinada função para um certo padrão que no caso são os tipos (Type).
  • 3 — "{ model | campo = valor }": essa sintaxe é utilizada para atualizar determinados campos de um Record e manter os demais.

Main

A função main é o ponto de entrada da nossa aplicação Elm, é nela que definimos que tipo de program iremos utilizar na app. Um program descreve como a aplicação será gerenciada pelo runtime do Elm. No nosso caso iremos utilizar o beginnerProgram, caso queira estudar os outros tipos basta consultar a documentação oficial.

O beginnerProgram é uma função do módulo Html que recebe um Record contendo o model, view e o update.

Em seguira é só compilar o código e abrir o arquivo index.html no seu navegador e nossa aplicação já estará funcionando.

elm-make Main.elm

Caso esteja utilizando o Ellie, basta clicar no botão “compile” no menu superior.

Instalação

A instalação é bastante simples e pode ser feita através dos instaladores para Mac, Windows e também através do npm:

npm install -g elm

Depois da instalação teremos alguns comandos disponíveis no terminal:

  • elm-repl: Interpretador interativo do Elm;
elm-repl
  • elm-reactor: Inicia um servidor de desenvolvimento;
elm-reactor
  • elm-make: Compila o código Elm;
  • elm-package: Gerenciador de dependências do Elm;
elm-package

Conclusão

Elm não é sobre substituir HTML, CSS e Javascript. Elm é sobre dar uma melhor experiência de desenvolvimento para os desenvolvedores, ajudando a escrever um código mais confiável e livre de erros, encorajando a refatorar a aplicação sem ter medo de quebra-la e nos possibilita focar no que realmente importa, o produto, sem que precise perder horas corrigindo bugs ou decidindo qual framework ou biblioteca Javascript usar no projeto.

Nas últimas semanas decidi refazer uma aplicação que tinha desenvolvido em AngularJs, desta vez em Elm. O resultado foi incrível, o código ficou bastante limpo, livre de erros (mesmo sem testes) e tudo isso sem muito esforço. O app passou por uma refatoração relativamente grande, apesar disso, não houve grandes esforços para concluí-la. Foram algumas horas refatorando, sempre com o auxílio do compilador, no término tudo estava funcionando como deveria. Caso queira dar umas espiada, o código está disponível no Github.