Programação Funcional em Kotlin: de A a Z
Programação funcional (PF) é um paradigma que trata a computação como a avaliação de funções matemáticas e evita a mudança de estado e dados mutáveis. Kotlin, sendo uma linguagem multiparadigma, fornece ferramentas poderosas para aplicar a abordagem funcional sem forçar você a abandonar a POO familiar. Neste artigo, abordaremos os conceitos-chave de PF em Kotlin: desde funções puras e imutabilidade até técnicas avançadas, como composição e mônadas.
1. Fundamentos da Abordagem Funcional: Funções Puras e Imutabilidade
A base da PF é construída sobre dois princípios: pureza de funções e imutabilidade de dados.
- Função pura é uma função cujo resultado depende apenas dos argumentos passados e que não tem efeitos colaterais (não altera variáveis externas, não imprime no console, não acessa o banco de dados).
- Imutabilidade significa que os dados não podem ser alterados após a criação. Em vez de modificar um objeto existente, uma nova cópia é criada com as alterações desejadas.
Exemplo de função pura e impura em Kotlin:
// Função impura: tem efeito colateral (impressão no console)fun impureSum(a: Int, b: Int): Int { println("Soma: ${a + b}") // efeito colateral return a + b}
// Função pura: apenas cálculofun pureSum(a: Int, b: Int): Int = a + b
// Imutabilidade: uso de val em vez de vardata class User(val name: String, val age: Int) // val torna os campos imutáveisfun main() { val user = User("Alice", 30) // user.age = 31 // Erro de compilação! val olderUser = user.copy(age = 31) // cria uma nova cópia}Usar val e data class com copy() é a maneira padrão de garantir imutabilidade em Kotlin.
2. Funções de Ordem Superior e Expressões Lambda
Kotlin suporta funções como objetos de primeira classe. Isso significa que funções podem ser passadas como argumentos, retornadas de outras funções e armazenadas em variáveis. Funções que recebem ou retornam outras funções são chamadas de funções de ordem superior.
Expressões lambda são uma maneira compacta de criar funções anônimas. Sintaxe: { argumentos -> corpo }.
// Função de ordem superior: recebe uma função (Int, Int) -> Intfun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int { return operation(a, b)}
fun main() { // Expressões lambda val sum: (Int, Int) -> Int = { x, y -> x + y } val multiply: (Int, Int) -> Int = { x, y -> x * y }
println(operateOnNumbers(5, 3, sum)) // 8 println(operateOnNumbers(5, 3, multiply)) // 15
// Usando lambda "na hora" println(operateOnNumbers(10, 2, { a, b -> a - b })) // 8
// Se a lambda for o último argumento, pode ser colocada fora dos parênteses (trailing lambda) println(operateOnNumbers(10, 2) { a, b -> a / b }) // 5}A biblioteca padrão do Kotlin usa extensivamente funções de ordem superior: map, filter, reduce para coleções.
val numbers = listOf(1, 2, 3, 4, 5)val evenSquares = numbers .filter { it % 2 == 0 } // {2, 4} .map { it * it } // {4, 16}println(evenSquares) // [4, 16]3. Composição de Funções e Currying
Composição é o processo de combinar duas ou mais funções para criar uma nova função. Kotlin não tem um operador de composição embutido como Haskell, mas é fácil implementá-lo manualmente ou com funções de extensão.
// Composição de duas funçõesinfix fun ((B) -> C).compose(f: (A) -> B): (A) -> C = { a -> this(f(a)) }
fun main() { val toInt: (String) -> Int = { it.toInt() } val square: (Int) -> Int = { it * it }
val squ