Programação Funcional em Go: da Teoria à Prática
Go (Golang) não foi originalmente concebido como uma linguagem puramente funcional. No entanto, seu poderoso sistema de tipos, suporte a funções de primeira classe e closures permitem aplicar com sucesso os princípios da programação funcional (FP). Neste artigo, vamos explorar como usar o estilo funcional em Go para escrever código mais previsível, testável e seguro para concorrência.
1. Funções de Primeira Classe e Funções de Alta Ordem
Em Go, funções são objetos de primeira classe. Isso significa que podem ser atribuídas a variáveis, passadas como argumentos e retornadas de outras funções. Funções que trabalham com outras funções são chamadas de funções de alta ordem.
Exemplo: passando uma função como argumento
package main
import "fmt"
// Função de alta ordem: recebe uma função e a aplica a um númerofunc applyOperation(x int, op func(int) int) int { return op(x)}
func double(x int) int { return x * 2}
func square(x int) int { return x * x}
func main() { result1 := applyOperation(5, double) result2 := applyOperation(5, square)
fmt.Println("Double:", result1) // 10 fmt.Println("Square:", result2) // 25}Exemplo: retornando uma função de outra função (fábrica de funções)
package main
import "fmt"
// Retorna uma função que adiciona um valor específicofunc createAdder(x int) func(int) int { return func(y int) int { return x + y }}
func main() { addFive := createAdder(5) addTen := createAdder(10)
fmt.Println(addFive(3)) // 8 fmt.Println(addTen(3)) // 13}2. Closures em Go
Um closure é uma função que "lembra" variáveis do escopo externo, mesmo após esse escopo ter terminado sua execução. Closures são uma ferramenta poderosa para criar estado encapsulado e fábricas de funções.
Exemplo: gerador de sequência
package main
import "fmt"
func sequenceGenerator() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequenceGenerator()
fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// Novo gerador começa em 1 next2 := sequenceGenerator() fmt.Println(next2()) // 1}Importante: Tenha cuidado com closures em loops. Em versões antigas do Go (anteriores à 1.22), a variável i no loop era reutilizada, o que causava erros. A partir do Go 1.22, as variáveis do loop são capturadas por valor.
3. Funções Puras e Imutabilidade
Uma função pura é uma função que:
- Sempre retorna o mesmo resultado para os mesmos argumentos.
- Não tem efeitos colaterais (não altera estado externo, não escreve em arquivos/rede).
Go não tem suporte nativo para imutabilidade, mas podemos seguir este princípio por conta própria.
Exemplo: função pura vs função impura
package main
import "fmt"
// Função IMPURA: altera um slice externovar counter int
func impureAddToSlice(s []int, val int) []int { counter++ // efeito colateral return append(s, val)}
// Função PURA: cria um novo slicefunc pureAddToSlice(s []int, val int) []int { newSlice := make([]int, len(s)+1) copy(newSlice, s) newSlice[len(s)] = val return newSlice}
func main() { original := []int{1, 2, 3}
// Abordagem pura result := pureAddToSlice(original, 4) fmt.Println("Original:", original) // [1 2 3] - não foi alterado fmt.Println("Result:", result) // [1 2 3 4]}Dica: Para trabalhar com estruturas imutá