Programação Funcional em Go: do Imperativo ao Declarativo
Go (Golang) é tradicionalmente associado à programação imperativa e concorrente, no entanto, seu poderoso sistema de tipos e suporte a funções de primeira classe tornam possível a aplicação de muitos princípios da programação funcional (PF). Neste artigo, vamos analisar como aplicar expressões idiomáticas de PF em Go, sem sacrificar o desempenho e a legibilidade do código.
Princípios Básicos de PF no Contexto do Go
Programação funcional em Go não significa abandonar completamente o estado mutável. Em vez disso, usamos os seguintes conceitos:
- Funções Puras — funções cujo resultado depende apenas dos argumentos, sem efeitos colaterais.
- Imutabilidade (Immutability) — preferência por criar novas estruturas de dados em vez de modificar as existentes.
- Funções de Ordem Superior — funções que recebem outras funções como argumentos ou as retornam.
- Closures (Fechamentos) — funções que capturam variáveis do escopo externo.
Funções de Ordem Superior: map, filter, reduce
Go não possui genéricos embutidos (até a versão 1.18), portanto os clássicos map, filter, reduce são implementados através de interfaces ou cópia explícita. Vamos considerar um exemplo com números inteiros:
package main
import "fmt"
// Map — aplica uma função a cada elemento de um slicefunc Map(s []int, fn func(int) int) []int { result := make([]int, len(s)) for i, v := range s { result[i] = fn(v) } return result}
// Filter — mantém apenas os elementos que satisfazem a condiçãofunc Filter(s []int, fn func(int) bool) []int { var result []int for _, v := range s { if fn(v) { result = append(result, v) } } return result}
// Reduce — reduz um slice a um único valorfunc Reduce(s []int, init int, fn func(int, int) int) int { acc := init for _, v := range s { acc = fn(acc, v) } return acc}
func main() { nums := []int{1, 2, 3, 4, 5}
// Dobra cada elemento doubled := Map(nums, func(x int) int { return x * 2 }) fmt.Println("Map:", doubled) // [2 4 6 8 10]
// Mantém apenas os pares evens := Filter(nums, func(x int) bool { return x%2 == 0 }) fmt.Println("Filter:", evens) // [2 4]
// Soma todos os elementos sum := Reduce(nums, 0, func(acc, x int) int { return acc + x }) fmt.Println("Reduce:", sum) // 15}Importante: No Go 1.18+ com genéricos, é possível criar versões universais:
// Generic Mapfunc MapGeneric[T, U any](s []T, fn func(T) U) []U { result := make([]U, len(s)) for i, v := range s { result[i] = fn(v) } return result}Closures e Currying em Go
Closures em Go são uma ferramenta poderosa para criar funções com preservação de estado. Vamos considerar um exemplo de gerador de sequência:
package main
import "fmt"
// Gerador da sequência de Fibonaccifunc fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a }}
// Currying: função que recebe argumentos em etapasfunc add(a int) func(int) int { return func(b int) int { return a + b }}
func main() { fib := fibonacci() fmt.Println(fib()) // 1 fmt.Println(fib()) // 1 fmt.Println(fib()) // 2
// Currying add5 := add(5) fmt.Println(add5(10)) // 15 fmt.Println(add5(20)) // 25}Closures são especialmente úteis para:
- Criar funções configuráveis (middleware)
- Implementar o padrão "fábrica de funções"
- Organizar mecanismos de callback