Funções como valores e tipos de função

1. Funções como Cidadãos de Primeira Classe

Em Go, funções são cidadãos de primeira classe, o que significa que podem ser atribuídas a variáveis, passadas como argumentos e retornadas de outras funções. Essa característica é fundamental para escrever código flexível e reutilizável.

package main

import "fmt"

// Atribuição de função a variável
var soma = func(a, b int) int {
    return a + b
}

func main() {
    resultado := soma(3, 4)
    fmt.Println(resultado) // 7

    // Função como argumento
    executarOperacao(10, 5, soma)
}

func executarOperacao(x, y int, operacao func(int, int) int) {
    fmt.Println("Resultado:", operacao(x, y))
}

2. Sintaxe de Tipos de Função

A declaração explícita de tipos de função melhora a legibilidade e permite a reutilização de assinaturas complexas.

package main

import "fmt"

// Declaração de tipo de função nomeado
type Operacao func(int, int) int

// Tipo anônimo vs nomeado
type Transformador func(string) string

func main() {
    var op Operacao = func(a, b int) int {
        return a * b
    }

    // Uso de tipo nomeado
    resultado := aplicarOperacao(6, 7, op)
    fmt.Println("Multiplicação:", resultado) // 42

    // Tipo anônimo inline
    aplicarOperacao(10, 2, func(a, b int) int {
        return a / b
    })
}

func aplicarOperacao(x, y int, op Operacao) int {
    return op(x, y)
}

3. Funções como Argumentos (Callbacks)

Funções de alta ordem que recebem outras funções como argumentos são extremamente úteis para processamento de dados.

package main

import "fmt"

type Filtro func(int) bool
type Mapeador func(int) int

func filtrar(numeros []int, fn Filtro) []int {
    var resultado []int
    for _, n := range numeros {
        if fn(n) {
            resultado = append(resultado, n)
        }
    }
    return resultado
}

func mapear(numeros []int, fn Mapeador) []int {
    resultado := make([]int, len(numeros))
    for i, n := range numeros {
        resultado[i] = fn(n)
    }
    return resultado
}

func reduzir(numeros []int, fn func(int, int) int, inicial int) int {
    acumulador := inicial
    for _, n := range numeros {
        acumulador = fn(acumulador, n)
    }
    return acumulador
}

func main() {
    dados := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // Filtro com função anônima
    pares := filtrar(dados, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Pares:", pares) // [2 4 6 8 10]

    // Mapeamento
    dobrados := mapear(pares, func(n int) int {
        return n * 2
    })
    fmt.Println("Dobrados:", dobrados) // [4 8 12 16 20]

    // Redução (soma)
    soma := reduzir(dobrados, func(acc, n int) int {
        return acc + n
    }, 0)
    fmt.Println("Soma:", soma) // 60
}

4. Funções como Valores de Retorno

Closures e fábricas de funções permitem criar funções personalizadas dinamicamente.

package main

import "fmt"

// Fábrica de funções
func criarMultiplicador(fator int) func(int) int {
    return func(x int) int {
        return x * fator
    }
}

// Closure com estado
func contador() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    // Usando fábrica de funções
    duplicar := criarMultiplicador(2)
    triplicar := criarMultiplicador(3)

    fmt.Println(duplicar(5))  // 10
    fmt.Println(triplicar(5)) // 15

    // Usando closure
    c1 := contador()
    fmt.Println(c1()) // 1
    fmt.Println(c1()) // 2
    fmt.Println(c1()) // 3

    // Cuidado: captura em loops (Go 1.22+ corrige isso)
    funcoes := make([]func(), 3)
    for i := 0; i < 3; i++ {
        i := i // Necessário em versões anteriores
        funcoes[i] = func() {
            fmt.Println(i)
        }
    }
    funcoes[0]() // 0
    funcoes[1]() // 1
    funcoes[2]() // 2
}

5. Comparação e Igualdade de Funções

Funções em Go só podem ser comparadas com nil, não entre si.

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var fn1 func(int) int = func(x int) int { return x * 2 }
    var fn2 func(int) int = func(x int) int { return x * 2 }

    // Comparação com nil
    if fn1 != nil {
        fmt.Println("fn1 não é nil")
    }

    // Tentativa de comparação direta (erro de compilação)
    // if fn1 == fn2 { ... } // Ilegal!

    // Uso de reflect para verificar igualdade
    same := reflect.ValueOf(fn1).Pointer() == reflect.ValueOf(fn2).Pointer()
    fmt.Println("Mesma função:", same) // false (diferentes closures)

    // Funções não podem ser chaves de map
    // m := make(map[func()]string) // Erro de compilação!
}

6. Tipos de Função e Interfaces

O padrão de adaptador permite que funções satisfaçam interfaces.

package main

import (
    "fmt"
    "net/http"
)

// Interface simples
type Logger interface {
    Log(mensagem string)
}

// Adaptador: função que implementa Logger
type LoggerFunc func(string)

func (f LoggerFunc) Log(mensagem string) {
    f(mensagem)
}

// Exemplo com http.HandlerFunc
func meuHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Olá, %s!", r.URL.Path[1:])
}

func main() {
    // Usando adaptador
    var logger Logger = LoggerFunc(func(msg string) {
        fmt.Println("[LOG]:", msg)
    })
    logger.Log("Iniciando servidor...")

    // http.HandlerFunc é outro exemplo de adaptador
    http.Handle("/", http.HandlerFunc(meuHandler))

    // Conversão implícita entre tipos compatíveis
    type MinhaFuncao func(int) int
    var f MinhaFuncao = func(x int) int { return x + 1 }
    var g func(int) int = f // Conversão implícita
    fmt.Println(g(5)) // 6
}

7. Métodos vs. Funções como Valores

Métodos podem ser tratados como valores de função de duas formas distintas.

package main

import "fmt"

type Calculadora struct {
    valor int
}

func (c Calculadora) Somar(x int) int {
    return c.valor + x
}

func (c *Calculadora) Incrementar(x int) {
    c.valor += x
}

func main() {
    calc := Calculadora{valor: 10}

    // Method value: mantém o receptor
    somar := calc.Somar
    fmt.Println(somar(5)) // 15

    // Method expression: receptor como primeiro parâmetro
    somarExp := Calculadora.Somar
    fmt.Println(somarExp(calc, 5)) // 15

    // Com ponteiro
    incrementar := calc.Incrementar
    incrementar(3)
    fmt.Println(calc.valor) // 13

    // Passando método como callback
    numeros := []int{1, 2, 3}
    for _, n := range numeros {
        fmt.Println(somar(n)) // 14, 15, 16
    }
}

8. Boas Práticas e Padrões Comuns

package main

import (
    "fmt"
    "sort"
)

// Boa prática: nomear tipos de função claramente
type Comparador func(a, b int) bool
type Processador func(string) string

// Evitar complexidade excessiva
func processarLista(itens []string, fn Processador) []string {
    resultado := make([]string, len(itens))
    for i, item := range itens {
        resultado[i] = fn(item)
    }
    return resultado
}

func main() {
    // Nomeação clara
    var cmp Comparador = func(a, b int) bool {
        return a < b
    }

    numeros := []int{3, 1, 4, 1, 5}
    sort.Slice(numeros, func(i, j int) bool {
        return cmp(numeros[i], numeros[j])
    })
    fmt.Println(numeros) // [1 1 3 4 5]

    // Quando preferir interfaces a tipos de função
    // Use interface quando houver múltiplos métodos
    // Use tipo de função para callbacks simples

    nomes := []string{"ana", "joão", "maria"}
    maiusculas := processarLista(nomes, func(s string) string {
        return string(s[0]-32) + s[1:] // Simulação de maiúscula
    })
    fmt.Println(maiusculas) // [Ana João Maria]
}

Referências