---
title: "Arrow em Kotlin — Programação Funcional na Prática | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/kotlin-arrow-programacao-funcional/"
markdown_url: "https://kotlin.dev.br/blog/kotlin-arrow-programacao-funcional.MD"
description: "Descubra o Arrow para Kotlin: Either, Option, Raise DSL e error handling funcional. Guia prático com exemplos reais em português."
date: "2026-03-25"
author: "Karina Melo"
---

# Arrow em Kotlin — Programação Funcional na Prática | Kotlin Brasil

Descubra o Arrow para Kotlin: Either, Option, Raise DSL e error handling funcional. Guia prático com exemplos reais em português.


Se você programa em Kotlin e já sentiu que o tratamento de erros com exceções é frágil, imprevisível e difícil de compor, a biblioteca **Arrow** pode mudar completamente sua perspectiva. Arrow é a principal biblioteca de programação funcional para Kotlin — e em 2026, com a **Raise DSL** e integração nativa com coroutines, ela está mais acessível e poderosa do que nunca.

## O que é Arrow?

Arrow é uma biblioteca open-source que traz conceitos de programação funcional tipada para Kotlin. Diferente de abordagens acadêmicas, Arrow foi projetada para ser **prática** — ela resolve problemas reais de tratamento de erros, composição de operações e modelagem de domínio.

Os pilares principais são:

- **Either** — representar sucesso ou falha sem exceções
- **Option** — lidar com ausência de valor de forma explícita
- **Raise DSL** — a abordagem moderna para error handling composável
- **Validated** — acumular múltiplos erros em vez de parar no primeiro

Arrow não é um framework que exige que você reescreva tudo. Você pode adotar uma peça de cada vez, no ritmo do seu projeto.

## Configurando Arrow no projeto

Adicione ao seu `build.gradle.kts`:

```kotlin
dependencies {
    implementation("io.arrow-kt:arrow-core:1.2.4")
    implementation("io.arrow-kt:arrow-fx-coroutines:1.2.4")
}
```

A dependência `arrow-core` traz Either, Option e Raise. Já `arrow-fx-coroutines` adiciona integração com coroutines do Kotlin para operações assíncronas.

## Either — Erros sem exceções

O problema com exceções é que elas são **invisíveis** na assinatura da função. Quando você chama `buscarUsuario(id)`, não tem como saber, olhando a assinatura, que ela pode lançar `UsuarioNaoEncontradoException`. Isso leva a bugs em produção.

`Either` resolve isso tornando o erro **explícito** no tipo de retorno:

```kotlin
import arrow.core.Either
import arrow.core.left
import arrow.core.right

sealed class ErroUsuario {
    data class NaoEncontrado(val id: Long) : ErroUsuario()
    data class EmailInvalido(val email: String) : ErroUsuario()
}

data class Usuario(val id: Long, val nome: String, val email: String)

fun buscarUsuario(id: Long): Either<ErroUsuario, Usuario> {
    val usuario = repository.findById(id)
    return if (usuario != null) {
        usuario.right()
    } else {
        ErroUsuario.NaoEncontrado(id).left()
    }
}
```

Agora, quem chama `buscarUsuario` **sabe** que a função pode falhar e é **obrigado** a lidar com isso:

```kotlin
when (val resultado = buscarUsuario(42L)) {
    is Either.Left -> println("Erro: ${resultado.value}")
    is Either.Right -> println("Encontrado: ${resultado.value.nome}")
}
```

A grande vantagem é a composição. Com `map`, `flatMap` e `fold`, você encadeia operações sem perder o controle:

```kotlin
fun processarPedido(usuarioId: Long): Either<ErroUsuario, Pedido> {
    return buscarUsuario(usuarioId)
        .map { usuario -> criarPedido(usuario) }
        .flatMap { pedido -> validarPedido(pedido) }
}
```

Se qualquer etapa falhar, o erro é propagado automaticamente — sem try/catch, sem exceções voando.

## Raise DSL — A abordagem moderna

A partir do Arrow 1.2, a **Raise DSL** se tornou a forma recomendada de trabalhar com erros. Ela combina a segurança do Either com uma sintaxe imperativa que parece código "normal":

```kotlin
import arrow.core.raise.either
import arrow.core.raise.Raise
import arrow.core.raise.ensure

fun Raise<ErroUsuario>.buscarUsuario(id: Long): Usuario {
    val usuario = repository.findById(id)
    ensure(usuario != null) { ErroUsuario.NaoEncontrado(id) }
    return usuario
}

fun Raise<ErroUsuario>.validarEmail(email: String): String {
    ensure(email.contains("@")) { ErroUsuario.EmailInvalido(email) }
    return email
}

fun Raise<ErroUsuario>.criarUsuario(nome: String, email: String): Usuario {
    val emailValidado = validarEmail(email)
    val usuario = Usuario(0L, nome, emailValidado)
    return repository.salvar(usuario)
}
```

O bloco `either { }` converte o resultado para `Either`:

```kotlin
val resultado: Either<ErroUsuario, Usuario> = either {
    val usuario = buscarUsuario(42L)
    val atualizado = usuario.copy(nome = "Novo Nome")
    repository.salvar(atualizado)
}
```

O código dentro do `either { }` parece imperativo — sem `map`, `flatMap` ou encadeamento manual. Mas a segurança é a mesma: se qualquer função com `Raise` falhar, a execução é interrompida e o erro é retornado como `Either.Left`.

Essa é a grande inovação do Raise: você escreve código que **parece** procedural mas **é** funcional.

## Option — Ausência explícita de valor

Kotlin já tem null safety com `?`, mas `Option` do Arrow ainda tem seu lugar quando você quer uma API mais funcional e composável:

```kotlin
import arrow.core.Option
import arrow.core.Some
import arrow.core.None
import arrow.core.toOption

fun buscarConfig(chave: String): Option<String> {
    return System.getenv(chave).toOption()
}

val porta = buscarConfig("PORT")
    .map { it.toInt() }
    .getOrElse { 8080 }
```

Na prática, muitos projetos Kotlin preferem usar `?` diretamente. Mas `Option` brilha quando você precisa de interoperabilidade com outras construções do Arrow ou quando trabalha com coleções de valores opcionais.

## Acumulando erros com Validated e zipOrAccumulate

Um caso comum é validação de formulários: você quer mostrar **todos** os erros de uma vez, não parar no primeiro. O Arrow oferece `zipOrAccumulate` para isso:

```kotlin
import arrow.core.raise.either
import arrow.core.raise.zipOrAccumulate

data class RegistroUsuario(val nome: String, val email: String, val idade: Int)

fun validarRegistro(
    nome: String,
    email: String,
    idade: Int
): Either<List<String>, RegistroUsuario> = either {
    zipOrAccumulate(
        { ensure(nome.isNotBlank()) { "Nome não pode estar vazio" } },
        { ensure(email.contains("@")) { "Email inválido" } },
        { ensure(idade >= 18) { "Deve ter pelo menos 18 anos" } }
    ) { _, _, _ ->
        RegistroUsuario(nome, email, idade)
    }
}
```

Se o nome estiver vazio **e** o email for inválido **e** a idade for menor que 18, todos os três erros são retornados juntos. Isso é impossível de fazer elegantemente com exceções.

## Arrow e coroutines

Arrow se integra perfeitamente com [coroutines do Kotlin](/blog/coroutines-kotlin/). Operações assíncronas podem ser compostas mantendo o tratamento de erros funcional:

```kotlin
import arrow.fx.coroutines.parZip

suspend fun carregarDashboard(userId: Long): Either<Erro, Dashboard> = either {
    parZip(
        { buscarPerfil(userId).bind() },
        { buscarPedidos(userId).bind() },
        { buscarNotificacoes(userId).bind() }
    ) { perfil, pedidos, notificacoes ->
        Dashboard(perfil, pedidos, notificacoes)
    }
}
```

O `parZip` executa as três operações **em paralelo** e combina os resultados. Se qualquer uma falhar, o erro é propagado e as outras são canceladas. É concorrência estruturada com error handling funcional — o melhor dos dois mundos. Se você trabalha com <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python e asyncio</a>, vai notar que o `parZip` oferece uma experiência mais segura que o `asyncio.gather`, já que o sistema de tipos garante o tratamento de erros em tempo de compilação.

## Por que Arrow em 2026?

O ecossistema Kotlin está amadurecendo rapidamente. Em 2026, vemos Arrow sendo adotado por equipes que buscam:

- **Código mais previsível** — sem surpresas de exceções não tratadas
- **Melhor testabilidade** — funções puras com Either são fáceis de testar (veja nosso [guia de testes com JUnit 5 e MockK](/blog/kotlin-testes-junit5-mockk-guia/))
- **Composição natural** — encadear operações que podem falhar sem aninhamento de try/catch
- **Interoperabilidade** — Arrow funciona com Spring Boot, Ktor, Android e qualquer projeto Kotlin

Se você já usa [DSLs em Kotlin](/blog/kotlin-dsl/), a Raise DSL do Arrow vai parecer completamente natural. A abordagem é a mesma: usar os recursos da linguagem para criar APIs expressivas e seguras.

## Arrow vs. exceções — Quando usar cada um

Arrow não é para substituir **todas** as exceções. Aqui está uma diretriz prática:

| Cenário | Abordagem recomendada |
|---|---|
| Erro de programação (bug) | Exceção (`IllegalStateException`) |
| Erro de domínio esperado | Either / Raise |
| Validação de entrada | zipOrAccumulate |
| Ausência de valor | Kotlin nullable (`?`) ou Option |
| Erro de infraestrutura | Exceção ou Either (depende do contexto) |

A regra geral: se o chamador **precisa** lidar com o erro como parte do fluxo normal, use Either. Se é um bug que não deveria acontecer, use exceções.

## Começando com Arrow gradualmente

Você não precisa adotar Arrow em todo o projeto de uma vez. Uma estratégia eficiente:

1. **Comece pela camada de serviço** — substitua exceções de domínio por Either
2. **Adote Raise DSL** nos novos serviços
3. **Use zipOrAccumulate** em validações de formulários e APIs
4. **Integre com coroutines** para operações assíncronas

Cada passo traz benefícios imediatos sem exigir uma reescrita completa.

## Conclusão

Arrow transformou a forma como desenvolvedores Kotlin lidam com erros e efeitos colaterais. Com a Raise DSL, a barreira de entrada caiu drasticamente — você não precisa ser um expert em programação funcional para se beneficiar. O código fica mais seguro, mais composável e mais fácil de testar.

Se você está construindo aplicações Kotlin que precisam de robustez no tratamento de erros — seja backend com Spring Boot, APIs com Ktor ou apps Android — Arrow merece um lugar no seu toolbox. <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust também abraça paradigmas funcionais com Result types e pattern matching</a>, e <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go adota retorno explícito de erros como padrão da linguagem</a> — ambas abordagens que inspiram o uso de Either no Kotlin. Comece com Either, evolua para Raise, e descubra como programação funcional pode ser prática e idiomática em Kotlin.
