Programação Funcional em Kotlin: Nível Avançado
\n\nKotlin foi originalmente criado como uma linguagem multiparadigma que combina organicamente abordagens orientadas a objetos e funcionais. Graças a recursos como funções de ordem superior, expressões lambda, imutabilidade de dados (val) e um sistema de tipos poderoso, Kotlin permite implementar conceitos complexos de programação funcional (FP). Neste artigo, exploraremos técnicas avançadas de FP em Kotlin que ajudarão você a escrever código mais limpo, previsível e testável.
\n\n1. Functores e functores aplicativos
\n\nUm functor é uma estrutura de dados que suporta a operação map, que aplica uma função ao conteúdo da estrutura, preservando sua forma. Em Kotlin, os functores padrão são coleções e tipos semelhantes a Option (por exemplo, T? ou Result).
Implementação de um functor para Result
\nConsidere um exemplo com o tipo Result, que encapsula um valor de sucesso ou um erro. Podemos criar um functor implementando o método map:
sealed class Result<out T> {\n data class Success<T>(val value: T) : Result<T>()\n data class Error(val exception: Throwable) : Result<Nothing>()\n}\n\nfun <T, R> Result<T>.map(f: (T) -> R): Result<R> = when (this) {\n is Result.Success -> Result.Success(f(value))\n is Result.Error -> this // preserva o erro\n}\n\n// Uso\nval result: Result<Int> = Result.Success(5)\nval mapped = result.map { it * 2 } // Result.Success(10)\n\n\nUm functor aplicativo estende as capacidades de um functor comum: ele permite aplicar uma função que está dentro de um contexto (por exemplo, Result) a um valor dentro de um contexto. Para isso, usa-se o método ap.
fun <T, R> Result<(T) -> R>.ap(value: Result<T>): Result<R> = when (this) {\n is Result.Success -> value.map(f)\n is Result.Error -> this\n}\n\n// Exemplo: combinando dois resultados\nval add: (Int) -> (Int) -> Int = { a -> { b -> a + b } }\nval resultF: Result<(Int) -> (Int) -> Int> = Result.Success(add)\nval resultA: Result<Int> = Result.Success(3)\nval resultB: Result<Int> = Result.Success(7)\nval combined = resultF.ap(resultA).ap(resultB) // Result.Success(10)\n\n\n2. Mônadas: Option, Either e trabalho com efeitos colaterais
\n\nUma mônada é um tipo que implementa duas operações: unit (colocar um valor em um contexto) e flatMap (encadear operações mônadas). Em Kotlin, uma mônada embutida pode ser considerada T? (tipo anulável), onde unit é simplesmente o valor e flatMap é o operador ?.let ou fold.
Implementação da mônada Option
\n\nsealed class Option<out T> {\n data class Some<T>(val value: T) : Option<T>()\n object None : Option<Nothing>()\n}\n\nfun <T, R> Option<T>.flatMap(f: (T) -> Option<R>): Option<R> = when (this) {\n is Option.Some -> f(value)\n is Option.None -> Option.None\n}\n\nfun <T> T.toOption(): Option<T> = Option.Some(this)\n\n// Exemplo de encadeamento de operações\ndata class User(val id: Int, val name: String)\n\nfun findUser(id: Int): Option<User> = if (id == 1) Option.Some(User(1, "Alice")) else Option.None\nfun getEmail(user: User): Option<String> = Option.Some("${user.name}@example.com")\n\nval email = findUser(1).flatMap { getEmail(it) } // Option.Some("Alice@example.com")\n\n\nMônada Either para tratamento de erros
\nAo contrário de Option, Either armazena informações sobre o erro:
sealed class Either<out L, out R> {\n data class Left<L>(val value: L) : Either<L, Nothing>()\n