Primeiros passos com Elm

2021-10-09

Elm é uma linguagem funcional, com tipos estáticos, perfeita para desenvolver web apps de forma segura.

##Instalação

Você pode instalar Elm facilmente através do npm ou yarn:

$ npm i -g elm

Porém eu prefiro instalar o Elm via Homebrew:

$ brew install elm

Para garantir que o executável foi instalado corretamente, basta executar elm --version no terminal, por exemplo:

$ elm --version
0.19.1

##Primeiro app

No terminal, crie um novo diretório, por exemplo first-elm-app e entre no diretório:

$ mkdir first-elm-app && cd first-elm-app

Em seguida, inicialize o projeto com Elm neste diretório:

$ elm init

Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.1/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]:

Aqui talvez seja o seu primeiro contato com o compilador do Elm. É normal ele avisar tudo que vai ser feito antes de ser feito, nos mínimos detalhes. Estes detalhes inclusive são aplicados durante o desenvolvimento e a etapa de compilação.

Você pode não curtir a sintaxe de Elm, mas aposto que vai se apaixonar pelo compilador!

Seguindo, responda a pergunta do compilador teclando Enter.

Se você listar o conteúdo do diretório, verá que o Elm criou um arquivo chamado elm.json. Esse arquivo faz mais ou menos o mesmo trabalho do package.json em projetos Node.js.

$ ls
elm.json  src

Agora é um bom momento para inicializar um repositorio Git!

##Configurando a suíte de testes

Um dos pontos mais importantes em um projeto são os testes! A facilidade da escrita e execução dos testes é essencial. Vamos começar instalando o elm-test através do NPM.

$ npm i -g elm-test

elm-test funciona mais ou menos como o jest e depende de alguns outros pacotes. Vamos configurar o nosso app para aceitar testes:

$ elm-test init
Here is my plan:

  Add:
    elm/random               1.0.0
    elm-explorations/test    1.2.2

Would you like me to update your elm.json accordingly? [Y/n]:

Novamente o Elm avisa o que vai fazer e pede a confirmação!

Ao aceitar, o programa elm-test vai criar o arquivo de teste de exemplo tests/Example.elm, além de adicionar algumas dependências no arquivo elm.json.

tests/Example.elm
module Example exposing (..)

import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)


suite : Test
suite =
    todo "Implement our first test. See https://package.elm-lang.org/packages/elm-explorations/test/latest for how to do this!"

Você pode notar que foi criada uma pasta chamada elm-stuff com algumas coisas dentro. Essa pasta é equivalente ao node_modules.

$ tree
.
├── elm-stuff
│   └── generated-code
│       └── elm-community
│           └── elm-test
│               └── 0.19.1-revision7
│                   └── install
│                       ├── elm-stuff
│                       │   └── 0.19.1
│                       │       ├── d.dat
│                       │       ├── i.dat
│                       │       └── o.dat
│                       └── elm.json
├── elm.json
├── src
└── tests
    └── Example.elm

10 directories, 6 files

Para evitar de adicionar a pasta elm-stuff no histórico do Git, vou ignorar ela criando um arquivo .gitignore:

$ echo /elm-stuff > .gitignore

O arquivo de exemplo com os testes não possui muitas coisas, mas pode ser amendrontador para quem nunca viu algum programa escrito em uma sintaxe parecida com Haskell ou a família das linguagens ML.

Elm é muito inspirada em Haskell, e mesmo que possa parecer assustador em primeiro momento, não é um bicho de sete cabeças.

Para executar os testes, basta executar elm-test no terminal:

$ elm-test
Compiling > Starting tests

elm-test 0.19.1-revision7
-------------------------

Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 99034205465976


TEST RUN INCOMPLETE because there is 1 TODO remaining

Duration: 132 ms
Passed:   0
Failed:   0
Todo:     1
↓ Example
◦ TODO: Implement our first test. See https://package.elm-lang.org/packages/elm-explorations/test/latest for how to do this!

Mesmo sem entender a sintaxe do Elm, pelo output do elm-test já dá pra entender que o programa conseguiu encontrar o nosso arquivo de teste, tentou executar os testes escritos naquele arquivo e encontrou um teste incompleto (que na verdade é o único escrito).

Vamos testar alguma coisa útil, se 1+1 é igual a 2:

tests/Example.elm
module Example exposing (..)

import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)


suite : Test
suite =
    test "Should 1+1 be equal to 1"
        (\_ -> Expect.equal (1+1) 2)

Ao executar os testes novamente, tudo verde.

$ elm-test
Compiling > Starting tests

elm-test 0.19.1-revision7
-------------------------

Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 4535202811949


TEST RUN PASSED

Duration: 98 ms
Passed:   1
Failed:   0

Mas provavelmente você está se perguntando o que diabos é isso aqui (\_ -> Expect.equal (1+1) 2).

Elm tem suporte a funções anônimas assim como em JavaScript. Traduzindo esse trecho de código em JavaScript, seria algo tipo:

;() => Expect.equal(1 + 1, 1)

Em Elm a sintaxe é um pouco diferente. Aqui vai mais um exemplo de função anonima:

\x -> x + 1
\x y -> x + y

Em JavaScript seria:

(x) => x + 1
(x, y) => x + y

A função test, importada do módulo Test aceita um título para o teste e uma função que executa o teste. No caso do arquivo de exemplo, é mais prático passar uma função anônima.

Usando JavaScript e Jest, o mesmo teste poderia ter a seguinte cara:

test('Should 1+1 be equal to 1', () => Expect.equal(1 + 1, 2))

Vou alterar o arquivo de teste e incluir a função describe:

tests/Example.elm
module Example exposing (..)

import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)

suite : Test
suite =
    describe "Example tests"
        [ test "Should 1+1 be equal to 1"
            (\_ -> Expect.equal (1 + 1) 2)
        ]

A função describe aceita um título e uma lista com testes. Vamos adicionar um novo teste e checar se a concatenação "hello" ++ "world" é igual a "hello world":

tests/Example.elm
module Example exposing (..)

import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)


suite : Test
suite =
    describe "Example tests"
        [ test "Should 1+1 be equal to 1"
            (\_ -> Expect.equal (1 + 1) 2)
        , test "Should concat 'hello' and 'world'"
            (\_ -> Expect.equal ("hello " ++ "world") "hello world")
        ]

Ao executar elm-test vemos que os dois testes foram executados com sucesso!

$ elm-test
Compiling > Starting tests

elm-test 0.19.1-revision7
-------------------------

Running 2 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 72391561032834


TEST RUN PASSED

Duration: 184 ms
Passed:   2
Failed:   0

##Feedback automático

Ao trabalhar com TDD, é muito prático colocar o test runner para executar automaticamente sempre que um arquivo do código fonte for alterado. É possível fazer isso com o elm-test usando o parâmetro --watch:

$ elm-test --watch
Compiling > Starting tests

elm-test 0.19.1-revision7
-------------------------

Running 2 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 36570578976867


TEST RUN PASSED

Duration: 159 ms
Passed:   2
Failed:   0

Watching for changes...

Vou alterar o arquivo tests/Example.elm e introduzir um erro:

tests/Example.elm
        [ test "Should 1+1 be equal to 1"
            (\_ -> Expect.equal (1 + 1) 1)

O elm-test automaticamente compilou o código fonte e executou os testes, mostrando o erro:

↓ Example
↓ Example tests
✗ Should 1+1 be equal to 1

    1
    ╷
    │ Expect.equal
    ╵
    2



TEST RUN FAILED

Duration: 219 ms
Passed:   1
Failed:   1

Watching for changes...

Caso eu use alguma função de asserção que não exista, como por exemplo:

tests/Example.elm
        [ test "Should 1+1 be equal to 1"
            (\_ -> Expect.dummy (1 + 1) 2)

O compilador me avisa de uma forma muito amigável:

I cannot find a `Expect.dummy` variable:

12|             (\_ -> Expect.dummy (1 + 1) 2)
                       ^^^^^^^^^^^^
The `Expect` module does not expose a `dummy` variable. These names seem close
though:

    Expect.all
    Expect.atMost
    Expect.equal
    Expect.err

##Recursos

Se você gostou desse tutorial, talvez possa se interessar pelas fontes que usei.

#elm#functional programming#tutorial

📝 Edite esta página