Escrevendo o Monad `Result` do Zero em Java
Escrevendo o Monad Result
do Zero em Java
Nesse artigo, a gente vai passar pelo processo de criar um Monad Result
em Java. A classe Result
é uma classe utilitária feita pra encapsular o resultado de uma operação que pode dar certo ou errado. Ela guarda ou um valor do tipo V
ou uma exceção indicando um erro. Essa classe é útil pra lidar com operações que podem dar certo ou falhar, oferecendo uma maneira clara e funcional de gerenciar os estados de sucesso e erro.
1. Definindo a Classe Result
Primeiro, vamos definir a classe Result
com dois campos: value
e error
. Esses campos vão guardar o resultado bem-sucedido e a exceção, respectivamente.
package com.hogemann.monad;
public class Result<V> {
private final V value;
private final Exception error;
private Result(V value, Exception error) {
this.value = value;
this.error = error;
}
}
2. Adicionando Métodos Estáticos
Agora, vamos adicionar métodos estáticos pra criar instâncias da classe Result
. Esses métodos funcionam como funções que encapsulam um valor ou um erro no contexto do Result
.
ok(V value)
: Cria uma instância deResult
representando uma operação bem-sucedida.error(Exception error)
: Cria uma instância deResult
representando uma operação com falha.empty()
: Cria uma instância vazia deResult
, sem valor ou erro.
public static <V> Result<V> ok(V value) {
return new Result<>(value, null);
}
public static <V> Result<V> error(Exception error) {
return new Result<>(null, error);
}
public static <V> Result<V> empty() {
return new Result<>(null, null);
}
3. Adicionando Métodos de Instância
Vamos adicionar métodos de instância pra pegar o valor ou o erro e pra checar o estado do Result
.
get()
: Retorna o valor se a operação deu certo.error()
: Retorna a exceção se a operação falhou.isOk()
: Verifica se a operação foi bem-sucedida.isEmpty()
: Verifica se o resultado tá vazio.
public V get() {
return this.value;
}
public Exception error() {
return this.error;
}
public boolean isOk() {
return this.error == null && this.value != null;
}
public boolean isEmpty() {
return this.value == null && this.error == null;
}
4. Adicionando Operações de Mônada
Pra fazer da classe Result
um Monad, precisamos adicionar os métodos map
e flatMap
. Esses métodos permitem encadear operações enquanto mantém o contexto.
map(Function<V,U> mapper)
: Transforma o valor usando a função passada se a operação foi bem-sucedida.flatMap(Function<V,Result<U>> mapper)
: Transforma o valor em outroResult
usando a função passada se a operação deu certo.
public <U> Result<U> map(Function<V, U> mapper) {
if (isOk()) {
return ok(mapper.apply(value));
} else {
return error(error);
}
}
public <U> Result<U> flatMap(Function<V, Result<U>> mapper) {
if (isOk()) {
return mapper.apply(value);
} else {
return error(error);
}
}
5. Adicionando Métodos Utilitários
Por fim, a gente vai adicionar um método utilitário pra executar ações de acordo com o resultado da operação.
ifOkOrElse(Consumer<V> consumer, Consumer<Exception> errorConsumer)
: Executa o consumer certo dependendo se a operação foi bem-sucedida ou não.
public void ifOkOrElse(Consumer<V> consumer, Consumer<Exception> errorConsumer) {
if (isOk()) {
consumer.accept(value);
} else {
errorConsumer.accept(error);
}
}
Classe Result
Completa
Aqui tá a classe Result
completa:
package com.hogemann.monad;
import java.util.function.Consumer;
import java.util.function.Function;
public class Result<V> {
private final V value;
private final Exception error;
private Result(V value, Exception error) {
this.value = value;
this.error = error;
}
public static <V> Result<V> ok(V value) {
return new Result<>(value, null);
}
public static <V> Result<V> error(Exception error) {
return new Result<>(null, error);
}
public static <V> Result<V> empty() {
return new Result<>(null, null);
}
public V get() {
return this.value;
}
public Exception error() {
return this.error;
}
public boolean isOk() {
return this.error == null && this.value != null;
}
public boolean isEmpty() {
return this.value == null && this.error == null;
}
public <U> Result<U> map(Function<V, U> mapper) {
if (isOk()) {
return ok(mapper.apply(value));
} else {
return error(error);
}
}
public <U> Result<U> flatMap(Function<V, Result<U>> mapper) {
if (isOk()) {
return mapper.apply(value);
} else {
return error(error);
}
}
public void ifOkOrElse(Consumer<V> consumer, Consumer<Exception> errorConsumer) {
if (isOk()) {
consumer.accept(value);
} else {
errorConsumer.accept(error);
}
}
}
Exemplos de Uso
Pra mostrar como usar o Monad Result
, vamos ver alguns exemplos práticos.
Exemplo 1: Lidando com uma Operação Bem-Sucedida
Nesse exemplo, vamos simular uma operação que deu certo e retorna um Result
contendo um valor.
public class Example {
public static void main(String[] args) {
Result<String> successResult = Result.ok("Operação deu certo");
successResult.ifOkOrElse(
value -> System.out.println("Sucesso: " + value),
error -> System.err.println("Erro: " + error.getMessage())
);
}
}
Saída:
Sucesso: Operação deu certo
Exemplo 2: Lidando com uma Operação com Falha
Aqui, simulamos uma operação que deu errado e retorna um Result
contendo um erro.
public class Example {
public static void main(String[] args) {
Result<String> errorResult = Result.error(new Exception("Operação falhou"));
errorResult.ifOkOrElse(
value -> System.out.println("Sucesso: " + value),
error -> System.err.println("Erro: " + error.getMessage())
);
}
}
Saída:
Erro: Operação falhou
Exemplo 3: Encadeando Operações com map
Esse exemplo mostra como transformar o valor dentro de um Result
usando o método map
.
public class Example {
public static void main(String[] args) {
Result<Integer> initialResult = Result.ok(5);
Result<Integer> transformedResult = initialResult.map(value -> value * 2);
transformedResult.ifOkOrElse(
value -> System.out.println("Valor Transformado: " + value),
error -> System.err.println("Erro: " + error.getMessage())
);
}
}
Saída:
Valor Transformado: 10
Exemplo 4: Encadeando Operações com flatMap
Nesse exemplo, a gente usa o método flatMap
pra encadear operações que retornam instâncias de Result
.
public class Example {
public static void main(String[] args) {
Result<Integer> initialResult = Result.ok(5);
Result<Integer> finalResult = initialResult.flatMap(value -> Result.ok(value * 2));
finalResult.ifOkOrElse(
value -> System.out.println("Valor Final: " + value),
error -> System.err.println("Erro: " + error.getMessage())
);
}
}
Saída:
Valor Final: 10
Esses exemplos mostram como o Monad Result
pode ser usado pra lidar com operações que podem dar certo ou errado e como encadear operações de uma maneira funcional.