Last active
September 4, 2024 13:40
-
-
Save zoedsoupe/47940db2f7d3d82a78b664cdbf31606e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Result do | |
@moduledoc """ | |
O módulo `Result` fornece funções utilitárias para lidar com resultados que podem ser bem-sucedidos (`:ok` ou `{:ok, term}`) ou erros (`{:error, term}`). Este módulo ajuda a gerenciar o fluxo de controle e tratamento de erros de forma clara e funcional. | |
### Mônada Result (ou Either) | |
A ideia por trás da mônada Result (ou Either) é encapsular a lógica de sucesso e erro em um único tipo de dados. Em vez de lançar exceções ou lidar com valores nulos, a mônada Result permite que as operações retornem explicitamente um valor de sucesso ou um erro, promovendo um estilo de programação mais seguro e robusto. | |
No Elixir, utilizamos o formato `{:ok, term}` para indicar sucesso e `{:error, term}` para indicar falha. Isso permite compor operações sequenciais sem a necessidade de constantes verificações de erro, utilizando funções como `and_then/2` para encadear operações e `or_else/2` para tratar erros. | |
### Referências para Estudo | |
- [Mônadas em Programação](https://en.wikipedia.org/wiki/Monad_(functional_programming)) - Explicação geral sobre mônadas na programação funcional. | |
- [Result and Option in Rust](https://doc.rust-lang.org/std/result/) - Documentação sobre o tipo `Result` em Rust, que é uma inspiração para esse padrão em Elixir. | |
- [Understanding Either Monad in Haskell](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/either-monad) - Explicação detalhada sobre a mônada Either em Haskell, outra fonte de inspiração. | |
""" | |
@opaque left(a) :: {:error, a} | |
@opaque right(b) :: :ok | {:ok, b} | |
@type t(a, b) :: left(a) | right(b) | |
@type t :: left(term) | right(term) | |
@doc """ | |
Cria um novo `Result` representando uma operação de sucesso | |
## Exemplos | |
iex> Result.ok() | |
:ok | |
iex> Result.ok(%{foo: "bar"}) | |
{:ok, %{foo: "bar"}} | |
""" | |
@spec ok :: right(term) | |
@spec ok(term) :: right(term) | |
def ok, do: :ok | |
def ok(val), do: {:ok, val} | |
@doc """ | |
Cria um novo `Result` representando uma operação de erro | |
## Exemplos | |
iex> Result.err("Não foi possível enviar este email") | |
{:error, "Não foi possível enviar este email"} | |
iex> Result.err({:rate_limited, "Número máximo de acessos"}) | |
{:error, {:rate_limited, "Número máximo de acessos"}} | |
""" | |
@spec err(term) :: left(term) | |
def err(reason), do: {:error, reason} | |
@doc """ | |
Extrai o resultado para obter o valor ou erro contido. | |
## Exemplos | |
iex> Result.unwrap(:ok) | |
nil | |
iex> Result.unwrap({:ok, 42}) | |
42 | |
iex> Result.unwrap({:error, "Algo deu errado"}) | |
"Algo deu errado" | |
""" | |
@spec unwrap(t) :: term | |
def unwrap(:ok), do: nil | |
def unwrap({:ok, data}), do: data | |
def unwrap({:error, err}), do: err | |
@doc """ | |
Aplica uma função ao valor contido em um `{:ok, term}` e retorna um novo `Result`. | |
## Exemplos | |
iex> result = {:ok, 2} | |
iex> Result.and_then(result, fn x -> {:ok, x * 2} end) | |
{:ok, 4} | |
iex> result = {:error, "Algo deu errado"} | |
iex> Result.and_then(result, fn x -> {:ok, x * 2} end) | |
{:error, "Algo deu errado"} | |
""" | |
@spec and_then(t, (t -> t)) :: t | |
def and_then(:ok, _fun), do: :ok | |
def and_then({:error, _} = err, _fun), do: err | |
def and_then({:ok, data}, fun) do | |
case fun.(data) do | |
{:ok, new_data} -> {:ok, new_data} | |
{:error, reason} -> {:error, reason} | |
:ok -> :ok | |
end | |
end | |
@doc """ | |
Executa uma função para tentar corrigir um erro, se houver. | |
## Exemplos | |
iex> result = {:error, "Erro inicial"} | |
iex> Result.or_else(result, fn _ -> {:ok, 42} end) | |
{:ok, 42} | |
iex> result = {:ok, 10} | |
iex> Result.or_else(result, fn _ -> {:ok, 42} end) | |
{:ok, 10} | |
""" | |
@spec or_else(t, (t -> t)) :: t | |
def or_else(:ok, _fun), do: :ok | |
def or_else({:ok, data}, _fun), do: {:ok, data} | |
def or_else({:error, err}, fun), do: fun.(err) | |
@doc """ | |
Aplica uma função ao erro contido em um `{:error, term}` e retorna um novo `Result`. | |
## Exemplos | |
iex> result = {:error, "Erro inicial"} | |
iex> Result.map_error(result, fn _ -> "Erro mapeado" end) | |
{:error, "Erro mapeado"} | |
iex> result = {:ok, 10} | |
iex> Result.map_error(result, fn _ -> "Erro mapeado" end) | |
{:ok, 10} | |
""" | |
@spec map_error(t, (t -> t)) :: t | |
def map_error(:ok, _fun), do: :ok | |
def map_error({:ok, _} = ok, _fun), do: ok | |
def map_error({:error, err}, fun), do: {:error, fun.(err)} | |
@doc """ | |
Retorna o valor contido em um `{:ok, term}`, ou um valor padrão se for um `{:error, term}`. | |
## Exemplos | |
iex> result = {:ok, 10} | |
iex> Result.unwrap_or(result, 42) | |
10 | |
iex> result = {:error, "Erro"} | |
iex> Result.unwrap_or(result, 42) | |
42 | |
""" | |
@spec unwrap_or(t, term) :: term | |
def unwrap_or(:ok, _default), do: :ok | |
def unwrap_or({:ok, data}, _default), do: data | |
def unwrap_or({:error, _}, default), do: default | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment