Programação Funcional em Go: Técnicas Avançadas e Exemplos
Go (Golang) não foi originalmente projetado como uma linguagem puramente funcional, no entanto, seus recursos poderosos, como closures, funções de primeira classe e goroutines, permitem aplicar com sucesso o paradigma da programação funcional (PF) em um nível avançado. Este artigo é destinado a desenvolvedores já familiarizados com os fundamentos do Go que desejam se aprofundar em técnicas funcionais para escrever código mais previsível, testável e concorrente. Abordaremos currying, composição de funções, trabalho com estruturas monádicas e integração de PF com goroutines.
1. Currying e Aplicação Parcial de Funções
Currying é a transformação de uma função que recebe vários argumentos em uma sequência de funções, cada uma recebendo um único argumento. Em Go, onde não há suporte nativo para currying, implementamos isso através de closures.
Exemplo: currying para um logger configurável
package main
import "fmt"
// Função curried: prefixo -> mensagem -> saídafunc createLogger(prefix string) func(string) { return func(message string) { fmt.Printf("[%s] %s", prefix, message) }}
func main() { infoLogger := createLogger("INFO") errorLogger := createLogger("ERROR")
infoLogger("Sistema iniciado") errorLogger("Erro de conexão") // Saída: // [INFO] Sistema iniciado // [ERROR] Erro de conexão}Aplicação parcial é um caso especial de currying, onde fixamos parte dos argumentos. Em Go, isso é particularmente útil para criar versões especializadas de funções.
// Aplicação parcial: fixamos o coeficientefunc multiply(a, b int) int { return a * b}
func partialMultiply(a int) func(int) int { return func(b int) int { return multiply(a, b) }}
func main() { double := partialMultiply(2) triple := partialMultiply(3)
fmt.Println(double(5)) // 10 fmt.Println(triple(5)) // 15}2. Composição de Funções e Pipelines
Composição é a combinação de funções simples em outras mais complexas. Em Go, podemos construir cadeias de transformações, passando o resultado de uma função como entrada para outra. Isso funciona de forma particularmente poderosa em combinação com interfaces e genéricos (Go 1.18+).
Exemplo: composição para processamento de strings
package main
import ( "fmt" "strings" "unicode")
type StringTransform func(string) string
// Compose cria a composição de duas funçõesfunc Compose(f, g StringTransform) StringTransform { return func(s string) string { return f(g(s)) }}
// Pipeline combina várias funçõesfunc Pipeline(transforms ...StringTransform) StringTransform { return func(s string) string { for _, t := range transforms { s = t(s) } return s }}
func removeSpaces(s string) string { return strings.ReplaceAll(s, " ", "")}
func toUpper(s string) string { return strings.ToUpper(s)}
func removeDigits(s string) string { var result strings.Builder for _, r := range s { if !unicode.IsDigit(r) { result.WriteRune(r) } } return result.String()}
func main() { // Composição de duas funções cleanAndUpper := Compose(removeSpaces, toUpper) result1 := cleanAndUpper("go 1.18 rocks") fmt.Println(result1) // "GO1.18ROCKS"
// Pipeline de três funções pipeline := Pipeline(removeSpaces, removeDigits, toUpper) result2 := pipeline("go 1.18 rocks") fmt.Println(result2) // "GOROCKS"}Esta abordagem permite construir transformações complexas a partir de blocos simples e testáveis. Cada função do pipeline pode ser testada separadamente.