Programação Funcional em Go: do A ao Z
Go (Golang) — uma linguagem frequentemente associada à programação imperativa e concorrente. No entanto, seu sistema de tipos flexível e funções de primeira classe permitem aplicar com sucesso o estilo funcional. Neste artigo, vamos explorar como usar a programação funcional (PF) em Go: desde conceitos básicos até exemplos práticos. Você aprenderá a escrever código mais limpo, previsível e testável.
O que é programação funcional no contexto do Go?
Programação funcional é um paradigma onde os cálculos são construídos com base na aplicação de funções, evitando mudanças de estado e efeitos colaterais. Em Go, isso se expressa através de:
- Funções puras — o resultado depende apenas dos argumentos, sem mutação de dados externos.
- Funções de alta ordem — funções que recebem outras funções como argumentos ou as retornam.
- Closures — funções anônimas que "lembram" variáveis do escopo externo.
- Imutabilidade de dados — preferência por cópia em vez de modificação.
Embora Go não seja uma linguagem puramente funcional (não possui mônadas ou tipos de dados algébricos), ele oferece ferramentas suficientes para o uso eficaz de abordagens de PF.
Funções anônimas e closures
Em Go, funções são objetos de primeira classe. Podem ser atribuídas a variáveis, passadas para outras funções e retornadas. Funções anônimas (lambdas) são a base do estilo funcional.
Exemplo de função anônima
package main
import "fmt"
func main() { // Função anônima atribuída a uma variável multiply := func(x, y int) int { return x * y }
result := multiply(5, 3) fmt.Println(result) // 15}Closures
Um closure é uma função anônima que "captura" variáveis do escopo externo. É uma ferramenta poderosa para criar geradores e funções com estado.
package main
import "fmt"
// Gerador de sequênciafunc sequence() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequence() fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// Cada chamada a sequence() cria um novo closure next2 := sequence() fmt.Println(next2()) // 1}Closures são ideais para encapsular estado sem usar variáveis globais — esta é a base da abordagem funcional em Go.
Funções puras e imutabilidade
Uma função pura não tem efeitos colaterais e não modifica os dados recebidos. Em Go, isso exige uma abordagem consciente, pois slices e maps são passados por referência.
Exemplo de função pura (sem mutação)
package main
import "fmt"
// Função pura: não modifica o slice originalfunc doubleValues(input []int) []int { result := make([]int, len(input)) for i, v := range input { result[i] = v * 2 } return result}
func main() { original := []int{1, 2, 3, 4} doubled := doubleValues(original)
fmt.Println("Original:", original) // [1 2 3 4] fmt.Println("Doubled:", doubled) // [2 4 6 8]}Observe: criamos um novo slice em vez de modificar o original. Isso garante a imutabilidade — um princípio chave da PF.
Funções de alta ordem: map, filter, reduce
Embora Go não tenha métodos nativos map/filter/reduce para slices, é fácil implementá-los manualmente.
package main
import "fmt"
// Map — aplica uma função a cada elementofunc Map[T any](items []T, fn func(T) T) []T { result := make([]T, len(items)) for