Closures e Aplicação Parcial em Haskell

Closures e Aplicação Parcial em Haskell
Photo by Jeswin Thomas / Unsplash

Haskell, sendo uma linguagem de programação funcional, oferece recursos poderosos como closures e aplicação parcial. Vamos explorar esses conceitos com exemplos práticos.

1. Capturando Valores em uma Expressão Lambda

Em Haskell, uma expressão lambda é uma função anônima que pode capturar valores do seu escopo. Isso é útil para criar funções que mantêm um estado ou contexto.

Exemplo:

criarSaudacao :: String -> String
criarSaudacao nome = (\saudacao -> saudacao ++ ", " ++ nome)

Neste exemplo, a função criarSaudacao retorna uma expressão lambda que captura o valor de nome.

2. Usando Closures para Criar Novas Funções

Um closure é uma função que captura e mantém o estado de variáveis externas. No exemplo anterior, a expressão lambda captura nome, formando um closure.

Uso do Closure:

let saudacaoParaJoao = criarSaudacao "João"
print (saudacaoParaJoao "Olá")  -- Saída: "Olá, João"

A função saudacaoParaJoao é um closure que mantém o valor "João".

3. Simplificação com Aplicação Parcial

A aplicação parcial é uma técnica onde uma função é chamada com menos argumentos do que ela requer, retornando uma nova função que espera os argumentos restantes.

Exemplo de Aplicação Parcial:

somar :: Int -> Int -> Int
somar x y = x + y

somarCinco :: Int -> Int
somarCinco = somar 5

Aqui, somarCinco é uma aplicação parcial da função somar, onde o primeiro argumento é fixado em 5.

Uso:

print (somarCinco 10)  -- Saída: 15

A aplicação parcial é útil para criar funções mais específicas a partir de funções mais genéricas.

Closures e aplicação parcial são conceitos fundamentais em Haskell que permitem criar funções poderosas e flexíveis. Closures capturam e mantêm estados, enquanto a aplicação parcial permite a reutilização de funções com diferentes conjuntos de argumentos. Essas características são essenciais para a programação funcional, oferecendo um alto nível de abstração e reutilização de código.

Exemplo Prático

Vamos criar um exemplo prático em Haskell demonstrando o uso de closures e aplicação parcial, seguindo a metodologia de Desenvolvimento Orientado por Testes (BDD - Behavior-Driven Development). O projeto consistirá em uma função que gera saudações personalizadas e outra que soma um número fixo a outro número. Vamos detalhar cada passo, desde a criação do projeto até a execução dos testes.

Passo 1: Criar o Projeto

  1. Instalar o Stack: Certifique-se de ter o Haskell Stack instalado.
  2. Criar um Novo Projeto:
    stack new closureExample
    cd closureExample
    

Passo 2: Configurar o package.yaml

  1. Abra o arquivo package.yaml.
  2. Adicione a dependência hspec para testes:
    dependencies:
    - base >= 4.7 && < 5
    
    tests:
      closureExample-test:
        main:                Spec.hs
        source-dirs:         test
        dependencies:
        - closureExample
        - hspec
    

Passo 3: Escrever Testes

  1. Criar o Arquivo de Teste:

    • Crie um arquivo Spec.hs na pasta test.
  2. Escrever Testes:

    • Spec.hs deve parecer com isso:
      import Test.Hspec
      import Lib
      
      main :: IO ()
      main = hspec $ do
        describe "Closure e Aplicação Parcial" $ do
          it "gera uma saudação personalizada" $ do
            let saudacaoParaJoao = criarSaudacao "João"
            saudacaoParaJoao "Olá" `shouldBe` "Olá, João"
      
          it "soma cinco a um número" $ do
            somarCinco 10 `shouldBe` 15
      

Passo 4: Implementar o Código

  1. Modificar Lib.hs:
    • No diretório src, abra Lib.hs.
    • Adicione as funções que serão testadas:
      module Lib
        ( criarSaudacao
        , somarCinco
        ) where
      
      criarSaudacao :: String -> String -> String
      criarSaudacao nome = \saudacao -> saudacao ++ ", " ++ nome
      
      somar :: Int -> Int -> Int
      somar x y = x + y
      
      somarCinco :: Int -> Int
      somarCinco = somar 5
      

O código em questão é um exemplo de uma função em Haskell que demonstra o conceito de closures. Vamos analisá-lo detalhadamente:

criarSaudacao :: String -> String -> String
criarSaudacao nome = \saudacao -> saudacao ++ ", " ++ nome
  1. Declaração da Função:

    • criarSaudacao :: String -> String -> String é a assinatura da função. Ela indica que criarSaudacao é uma função que recebe uma String (o nome) e retorna uma função do tipo String -> String. Esta função retornada espera uma String (a saudacao) e também retorna uma String.
  2. Definição da Função:

    • criarSaudacao nome = \saudacao -> saudacao ++ ", " ++ nome é a definição da função. Aqui, criarSaudacao recebe um argumento nome, e retorna uma função lambda.
  3. Função Lambda:

    • \saudacao -> saudacao ++ ", " ++ nome é uma função lambda. Esta função anônima recebe um argumento saudacao e concatena (usando ++) esta saudacao com uma vírgula e o nome fornecido à função criarSaudacao.
  4. Closure:

    • O conceito de closure é demonstrado aqui. A função lambda \saudacao -> saudacao ++ ", " ++ nome "captura" o valor de nome que foi fornecido quando criarSaudacao foi chamada. Mesmo após a execução de criarSaudacao, a função lambda retida ainda tem acesso ao nome que foi passado para ela.

Exemplo de Uso:

Se você chamar criarSaudacao "João", isso retornará uma nova função que espera uma saudação. Por exemplo:

let saudacaoParaJoao = criarSaudacao "João"

Agora, saudacaoParaJoao é uma função que você pode usar para criar uma saudação personalizada para "João". Por exemplo:

saudacaoParaJoao "Olá" -- Isso resultará em "Olá, João"

Neste exemplo, a função saudacaoParaJoao é uma closure que mantém o valor "João" e o utiliza sempre que é chamada com uma nova saudação.

Passo 5: Modificar a Main.hs

  1. Alterar Main.hs:
    • No diretório app, edite Main.hs para usar as funções:
      module Main where
      
      import Lib
      
      main :: IO ()
      main = do
        let saudacaoParaJoao = criarSaudacao "João"
        print $ saudacaoParaJoao "Olá"
        print $ somarCinco 10
      

Passo 6: Executar os Testes

  1. Executar Testes:
    • Volte para o diretório raiz do projeto e execute:
      stack test
      
➜  closureExample stack test
closureExample-0.1.0.0: unregistering (local file changes: test/Spec.hs)
closureExample> build (lib + exe + test)
Preprocessing library for closureExample-0.1.0.0..
Building library for closureExample-0.1.0.0..
Preprocessing executable 'closureExample-exe' for closureExample-0.1.0.0..
Building executable 'closureExample-exe' for closureExample-0.1.0.0..
Preprocessing test suite 'closureExample-test' for closureExample-0.1.0.0..
Building test suite 'closureExample-test' for closureExample-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/closureExample-test/closureExample-test [Objects changed]
closureExample> copy/register
Installing library in /Users/ropeixoto/Project/youtube/haskell/closureExample/.stack-work/install/x86_64-osx/8306faa983f5abaefc1c0174cee0b0c2f3d9546bcf08dd2d53dbf61d40958b21/9.4.7/lib/x86_64-osx-ghc-9.4.7/closureExample-0.1.0.0-OyDDZm2yClIgqO83KmHyv
Installing executable closureExample-exe in /Users/ropeixoto/Project/youtube/haskell/closureExample/.stack-work/install/x86_64-osx/8306faa983f5abaefc1c0174cee0b0c2f3d9546bcf08dd2d53dbf61d40958b21/9.4.7/bin
Registering library for closureExample-0.1.0.0..
closureExample> test (suite: closureExample-test)


Closure e Apliocação Parcial
  gera uma saudação personalizada [✔]
  soma cinco a um número [✔]

Finished in 0.0008 seconds
2 examples, 0 failures



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

Passo 7: Executar o Projeto

  1. Executar o Projeto:
    • Para ver o projeto em ação, execute:
      stack run
      

Explicação do Código

  • criarSaudacao: Esta função demonstra um closure. Ela retorna uma função lambda que captura o nome e espera um saudacao para completar a mensagem.
  • somarCinco: Esta é uma aplicação parcial da função somar. Fixamos o primeiro argumento em 5 e retornamos uma nova função que espera o segundo argumento.

É isso skillers, até mais! <3

Read more