Desenvolvendo Funções de Divisão com TDD em Haskell

Desenvolvendo Funções de Divisão com TDD em Haskell
Photo by Ramón Salinero / Unsplash

Neste post, vamos aplicar TDD para desenvolver funções de divisão em Haskell, abordando desde divisões simples até o tratamento de divisão por zero.

Passo 1: Configurar o Ambiente

Certifique-se de ter o Stack, uma ferramenta de desenvolvimento Haskell, instalada.

Passo 2: Criar um Novo Projeto

No terminal, crie um novo projeto Stack:

stack new divisionProject
cd divisionProject

Passo 3: Adicionar Dependências de Teste

No arquivo package.yaml, adicione hspec às dependências de teste:

tests:
  divisionProject-test:
    main:                Spec.hs
    source-dirs:         test
    dependencies:
      - divisionProject
      - hspec

Passo 4: Escrever Testes

Crie um arquivo de teste em test/Spec.hs com os seguintes cenários:

import Test.Hspec
import Lib

main :: IO ()
main = hspec $ do
  describe "divisionFunction" $ do
    it "correctly divides two numbers" $ do
      divisionFunction 10 2 `shouldBe` Just 5

    it "handles division by a larger number" $ do
      divisionFunction 5 10 `shouldBe` Just 0.5

    it "returns Nothing when dividing by zero" $ do
      divisionFunction 5 0 `shouldBe` Nothing

Execute o teste com o comando:

stack test


Resultado

[1 of 2] Compiling Main

/Users/ropeixoto/Project/youtube/haskell/divisionProject/test/Spec.hs:8:7: error:
    Variable not in scope: divisionFunction :: t0 -> t1 -> Maybe a0
  |
8 |       divisionFunction 10 2 `shouldBe` Just 5
  |       ^^^^^^^^^^^^^^^^
[2 of 2] Compiling Paths_divisionProject
Progress 1/2

Error: [S-7282]
       Stack failed to execute the build plan.

       While executing the build plan, Stack encountered the error:

       [S-7011]
       While building package divisionProject-0.1.0.0 (scroll up to its
       section to see the error) using:
       /Users/ropeixoto/.stack/setup-exe-cache/x86_64-osx/Cabal-simple_6HauvNHV_3.8.1.0_ghc-9.4.7 --verbose=1 --builddir=.stack-work/dist/x86_64-osx/Cabal-3.8.1.0 build lib:divisionProject exe:divisionProject-exe test:divisionProject-test --ghc-options " -fdiagnostics-color=always"
       Process exited with code: ExitFailure 1
➜  divisionProject stack test
divisionProject> build (lib + exe + test)
Preprocessing library for divisionProject-0.1.0.0..
Building library for divisionProject-0.1.0.0..
Preprocessing executable 'divisionProject-exe' for divisionProject-0.1.0.0..
Building executable 'divisionProject-exe' for divisionProject-0.1.0.0..
Preprocessing test suite 'divisionProject-test' for divisionProject-0.1.0.0..
Building test suite 'divisionProject-test' for divisionProject-0.1.0.0..
[1 of 2] Compiling Main

/Users/ropeixoto/Project/youtube/haskell/divisionProject/test/Spec.hs:8:7: error:
    Variable not in scope: divisionFunction :: t4 -> t5 -> Maybe a2
  |
8 |       divisionFunction 10 2 `shouldBe` Just 5
  |       ^^^^^^^^^^^^^^^^

/Users/ropeixoto/Project/youtube/haskell/divisionProject/test/Spec.hs:11:7: error:
    Variable not in scope: divisionFunction :: t2 -> t3 -> Maybe a1
   |
11 |       divisionFunction 5 10 `shouldBe` Just 0.5
   |       ^^^^^^^^^^^^^^^^

/Users/ropeixoto/Project/youtube/haskell/divisionProject/test/Spec.hs:14:7: error:
    Variable not in scope: divisionFunction :: t0 -> t1 -> Maybe a0
   |
14 |       divisionFunction 5 0 `shouldBe` Nothing
   |       ^^^^^^^^^^^^^^^^
Progress 1/2

Error: [S-7282]
       Stack failed to execute the build plan.

       While executing the build plan, Stack encountered the error:

       [S-7011]
       While building package divisionProject-0.1.0.0 (scroll up to its
       section to see the error) using:
       /Users/ropeixoto/.stack/setup-exe-cache/x86_64-osx/Cabal-simple_6HauvNHV_3.8.1.0_ghc-9.4.7 --verbose=1 --builddir=.stack-work/dist/x86_64-osx/Cabal-3.8.1.0 build lib:divisionProject exe:divisionProject-exe test:divisionProject-test --ghc-options " -fdiagnostics-color=always"
       Process exited with code: ExitFailure 1

Passo 5: Implementar a Função

No arquivo src/Lib.hs, implemente a função divisionFunction:

module Lib
    ( divisionFunction
    ) where

divisionFunction :: Float -> Float -> Maybe Float
divisionFunction _ 0 = Nothing
divisionFunction x y = Just (x / y)

Agora altere o nome no arquivo app/Main.hs:

module Main (main) where

import Lib

main :: IO ()
main = print(divisionFunction 2 10)

Vamos decompor a sintaxe da função divisionFunction em Haskell para torná-la mais compreensível. Haskell tem uma sintaxe única, especialmente se você está acostumado com linguagens imperativas, mas uma vez que você entende a lógica por trás dela, tudo se torna mais claro.

Assinatura da Função

divisionFunction :: Float -> Float -> Maybe Float
  • divisionFunction é o nome da função.
  • :: é usado para denotar a assinatura da função.
  • Float -> Float -> Maybe Float descreve os tipos dos argumentos e do retorno da função.
    • Float -> Float indica que a função aceita dois argumentos, ambos do tipo Float.
    • -> Maybe Float indica que a função retorna um valor do tipo Maybe Float.

Corpo da Função

A função divisionFunction é definida usando correspondência de padrões (pattern matching), uma característica poderosa em Haskell.

divisionFunction _ 0 = Nothing
divisionFunction x y = Just (x / y)
  • divisionFunction _ 0 = Nothing: Esta linha lida com o caso de divisão por zero.
    • _ é um caractere curinga que significa "qualquer valor". Neste contexto, indica que o primeiro argumento pode ser qualquer Float.
    • 0 especifica que o segundo argumento deve ser zero.
    • = Nothing é o valor retornado pela função quando o segundo argumento é zero. Nothing é um valor do tipo Maybe Float, que é usado para representar a ausência de um valor válido (neste caso, porque não podemos dividir por zero).
  • divisionFunction x y = Just (x / y): Esta linha lida com todos os outros casos.
    • x e y são variáveis que representam os dois argumentos Float.
    • = Just (x / y) calcula a divisão de x por y e retorna o resultado embrulhado em Just. Just é um construtor para o tipo Maybe, que neste caso, encapsula um valor Float válido.

Passo 6: Executar o Teste

Execute o teste com o comando:

stack test

Se os testes passarem, sua função de divisão está correta e lida com os casos de divisão por zero.

➜  divisionProject stack test
divisionProject> build (lib + exe + test)
Preprocessing library for divisionProject-0.1.0.0..
Building library for divisionProject-0.1.0.0..
Preprocessing executable 'divisionProject-exe' for divisionProject-0.1.0.0..
Building executable 'divisionProject-exe' for divisionProject-0.1.0.0..
[1 of 2] Compiling Main [Source file changed]
[3 of 3] Linking .stack-work/dist/x86_64-osx/Cabal-3.8.1.0/build/divisionProject-exe/divisionProject-exe [Objects changed]
Preprocessing test suite 'divisionProject-test' for divisionProject-0.1.0.0..
Building test suite 'divisionProject-test' for divisionProject-0.1.0.0..
[1 of 2] Compiling Main
[3 of 3] Linking .stack-work/dist/x86_64-osx/Cabal-3.8.1.0/build/divisionProject-test/divisionProject-test
divisionProject> copy/register
Installing library in /Users/ropeixoto/Project/youtube/haskell/divisionProject/.stack-work/install/x86_64-osx/8306faa983f5abaefc1c0174cee0b0c2f3d9546bcf08dd2d53dbf61d40958b21/9.4.7/lib/x86_64-osx-ghc-9.4.7/divisionProject-0.1.0.0-BerzMQAkBpW5ecYpuZ6lkN
Installing executable divisionProject-exe in /Users/ropeixoto/Project/youtube/haskell/divisionProject/.stack-work/install/x86_64-osx/8306faa983f5abaefc1c0174cee0b0c2f3d9546bcf08dd2d53dbf61d40958b21/9.4.7/bin
Registering library for divisionProject-0.1.0.0..
divisionProject> test (suite: divisionProject-test)


divisionFunction
  correctly divides two numbers [✔]
  handles division by a larger number [✔]
  returns Nothing when dividing by zero [✔]

Finished in 0.0008 seconds
3 examples, 0 failures



divisionProject> Test suite divisionProject-test passed
Completed 2 action(s).

Agora já entendemos como funciona o tratamento de erros com o Haskel, vamos continuar avançando nos conteúdo. Há muito do que conhecermos e entendermos.

Encorajo vocês a mexerem, removerem os testes de divisão com zero e tentar dividir por zero no Main.hs e futucar no código, quebrar, ler os erros, isso ajuda vocês a compreenderem.

Valeu skillers,

Nos vemos em breve nos próximos posts. Até lá, happy coding! 🚀📚

Read more