Introdução à fuga Atlassian
1. Introdução
Fugue é uma biblioteca Java da Atlassian; é uma coleção de utilitários que oferecem suporte aFunctional Programming.
Neste artigo, vamos nos concentrar e explorar as APIs do Fugue mais importantes.
2. Introdução ao Fugue
Para começar a usar o Fugue em nossos projetos, precisamos adicionar a seguinte dependência:
io.atlassian.fugue
fugue
4.5.1
Podemos encontrar a versão mais recente deFugue no Maven Central.
3. Option
Vamos começar nossa jornada olhando para a classeOption, que é a resposta de Fugue parajava.util.Optional.
Como podemos adivinhar pelo nome,Option's a container representing a potentially absent value.
Em outras palavras, umOption é o valorSome de um certo tipo ouNone:
Option
3.1. A operação demap
Uma das APIs de programação funcional padrão é o métodomap(), que permite aplicar uma função fornecida aos elementos subjacentes.
O método aplica a função fornecida ao valor deOption se estiver presente:
Option some = Option.some("value")
.map(String::toUpperCase);
assertEquals("VALUE", some.get());
3.2. Optione um valor deNull
Além de diferenças de nomenclatura, Atlassian fez algumas escolhas de design paraOption que diferem deOptional; vamos agora olhar para eles.
We cannot directly create a non-empty Option holding a null value:
Option.some(null);
O exemplo acima gera uma exceção.
No entanto, podemos obter um como resultado do uso da operaçãomap():
Option
Isso não é possível simplesmente usandojava.util.Optional.
3.3. Option éIterable
Option pode ser tratado como uma coleção que contém no máximo um elemento, portanto, faz sentido implementar a interfaceIterable.
Isso aumenta muito a interoperabilidade ao trabalhar com coleções / fluxos.
E agora, por exemplo, pode ser concatenado com outra coleção:
Option some = Option.some("value");
Iterable strings = Iterables
.concat(some, Arrays.asList("a", "b", "c"));
3.4. ConvertendoOption emStream
Como umOption é umIterable,, ele também pode ser convertido emStream facilmente.
Após a conversão, a instânciaStream terá exatamente um elemento se a opção estiver presente, ou zero caso contrário:
assertEquals(0, Option.none().toStream().count());
assertEquals(1, Option.some("value").toStream().count());
3.5. java.util.Optional Interoperabilidade
Se precisarmos de uma implementaçãoOptional padrão, podemos obtê-la facilmente usando o métodotoOptional():
Optional
3.6. A classe de utilidadeOptions
Finalmente, o Fugue fornece alguns métodos utilitários para trabalhar comOptions na classeOptions apropriadamente nomeada.
Ele apresenta métodos comofilterNone para removerOptions vazio de uma coleção eflatten para transformaringa coleção deOptions em uma coleção de objetos fechados, filtrando fora vazioOptions.
Além disso, ele apresenta várias variantes do métodolift que eleva umFunction<A,B> em umFunction<Option<A>, Option<B>>:
Function f = (Integer x) -> x > 0 ? x + 1 : null;
Function
Isso é útil quando queremos passar uma função que não conheceOption para algum método que usaOption.
Observe que, assim como o métodomap,lift doesn’t map null to None:
assertEquals(null, lifted.apply(Option.some(0)).get());
4. Either para cálculos com dois resultados possíveis
Como vimos, a classeOption nos permite lidar com a ausência de um valor de uma maneira funcional.
No entanto, às vezes precisamos retornar mais informações do que "sem valor"; por exemplo, podemos querer retornar um valor legítimo ou um objeto de erro.
A classeEither cobre esse caso de uso.
Uma instância deEither pode ser umRight ou umLeft but never both at the same time.
Por convenção, a direita é o resultado de uma computação bem-sucedida, enquanto a esquerda é o caso excepcional.
4.1. Construindo umEither
Podemos obter uma instânciaEither chamando um de seus dois métodos de fábrica estáticos.
Chamamosright se quisermos umEither contendo o valorRight:
Either right = Either.right("value");
Caso contrário, chamamosleft:
Either left = Either.left(-1);
Aqui, nosso cálculo pode retornar umString ou umInteger.
4.2. Usando umEither
Quando temos uma instânciaEither, podemos verificar se está à esquerda ou à direita e agir de acordo:
if (either.isRight()) {
...
}
Mais interessante, podemos encadear operações usando um estilo funcional:
either
.map(String::toUpperCase)
.getOrNull();
4.3. Projeções
A principal coisa que o diferencia de outras ferramentas monádicas comoOption, Try, é o fato de que muitas vezes é imparcial. Simplificando, se chamarmos o método map (),Either não sabe se trabalhar comLeft ouRight lado.
É aqui que as projeções são úteis.
Left and right projections are specular views of an Either that focus on the left or right value, respectivamente:
either.left()
.map(x -> decodeSQLErrorCode(x));
No trecho de código acima, seEither forLeft, decodeSQLErrorCode(), será aplicado ao elemento subjacente. SeEither forRight,, não. O mesmo acontece ao usar a projeção correta.
4.4. Métodos Utilitários
Como comOptions, Fugue fornece uma classe cheia de utilitários paraEithers, também, e é chamado assim:Eithers.
Ele contém métodos para filtrar, lançar e iterar coleções deEithers.
5. Tratamento de exceções comTry
Concluímos nosso tour pelos tipos de dados um ou outro na Fuga com outra variação chamadaTry.
Try é semelhante aEither, mas difere por ser dedicado a trabalhar com exceções.
ComoOptione diferente deEither,Try é parametrizado em um único tipo, porque o "outro" tipo é fixo emException (enquanto paraOption é implicitamente Void).
Portanto, aTry pode ser aSuccess ou aFailure:
assertTrue(Try.failure(new Exception("Fail!")).isFailure());
assertTrue(Try.successful("OK").isSuccess());
5.1. Instanciando umTry
Freqüentemente, não criaremos umTry explicitamente como um sucesso ou uma falha; em vez disso, vamos criar um a partir de uma chamada de método.
Checked.of chama uma determinada função e retorna umTry encapsulando seu valor de retorno ou qualquer exceção lançada:
assertTrue(Checked.of(() -> "ok").isSuccess());
assertTrue(Checked.of(() -> { throw new Exception("ko"); }).isFailure());
Outro método,Checked.lift, pega uma função potencialmente lançadora elifts para uma função que retorna umTry:
Checked.Function throwException = (String x) -> {
throw new Exception(x);
};
assertTrue(Checked.lift(throwException).apply("ko").isFailure());
5.2. Trabalhando comTry
Assim que tivermos umTry, as três coisas mais comuns que podemos querer fazer com ele são:
-
extraindo seu valor
-
encadeando alguma operação para o valor bem-sucedido
-
manipulando a exceção com uma função
Além disso, obviamente, descartar oTry ou passá-lo para outros métodos, os três acima não são as únicas opções que temos, mas todos os outros métodos integrados são apenas uma conveniência sobre esses três.
5.3. Extraindo o valor de sucesso
Para extrair o valor, usamos o métodogetOrElse:
assertEquals(42, failedTry.getOrElse(() -> 42));
Retorna o valor bem-sucedido se presente, ou algum valor calculado caso contrário.
Não hágetOrThrow ou semelhante, mas comogetOrElse não captura nenhuma exceção, podemos escrevê-lo facilmente:
someTry.getOrElse(() -> {
throw new NoSuchElementException("Nothing to get");
});
5.4. Encadeando chamadas após o sucesso
Em um estilo funcional, podemos aplicar uma função ao valor de sucesso (se presente) sem extraí-la explicitamente primeiro.
Este é o métodomap típico que encontramos emOption,Eithere na maioria dos outros contêineres e coleções:
Try aTry = Try.successful(42).map(x -> x + 1);
Ele retorna umTry para que possamos encadear outras operações.
Claro, também temos a variedadeflatMap:
Try.successful(42).flatMap(x -> Try.successful(x + 1));
5.5. Recuperando-se de exceções
Temos operações de mapeamento análogas que funcionam com exceção de aTry (se presente), em vez de seu valor de sucesso.
No entanto, esses métodos diferem porque seu significado éto recover from the exception, i.e. to produce a successful Try no caso padrão.
Assim, podemos produzir um novo valor comrecover:
Try
Como podemos ver, a função de recuperação aceita a exceção como seu único argumento.
Se a própria função de recuperação lançar, o resultado será outroTry com falha:
Try
O análogo aflatMap é chamadorecoverWith:
Try
6. Outros utilitários
Vamos agora dar uma olhada rápida em alguns dos outros utilitários do Fugue, antes de encerrá-lo.
6.1. Pares
APair é uma estrutura de dados realmente simples e versátil, feita de dois componentes igualmente importantes, que Fugue chama delefteright:
Pair pair = Pair.pair(1, "a");
assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());
O Fugue não fornece muitos métodos integrados emPairs, além de mapeamento e o padrão de função do aplicativo.
No entanto,Pairs são usados em toda a biblioteca e estão prontamente disponíveis para programas do usuário.
A implementação do Lisp pela próxima pobre pessoa está a apenas algumas teclas de distância!
6.2. Unit
Unit é um enum com um único valor que se destina a representar “nenhum valor”.
É uma substituição para o tipo de retorno vazio e classeVoid, que acaba comnull:
Unit doSomething() {
System.out.println("Hello! Side effect");
return Unit();
}
Surpreendentemente, no entanto,Option doesn’t understand Unit, treating it like some value instead of none.
6.3. Utilitários estáticos
Temos algumas classes repletas de métodos utilitários estáticos que não precisaremos escrever e testar.
A classeFunctions oferece métodos que usam e transformam funções de várias maneiras: composição, aplicação, currying, funções parciais usandoOption, memoização fraca etc.
A classeSuppliers fornece uma coleção semelhante, mas mais limitada, de utilitários paraSuppliers, ou seja, funções sem argumentos.
IterableseIterators, finalmente, contêm uma série de métodos estáticos para manipular essas duas interfaces Java padrão amplamente utilizadas.
7. Conclusão
Neste artigo, demos uma visão geral da biblioteca Fugue da Atlassian.
Não tocamos nas classes pesadas de álgebra comoMonoideSemigroups porque elas não se encaixam em um artigo generalista.
No entanto, você pode ler sobre eles e muito mais na Fugajavadocsesource code.
Também não tocamos em nenhum dos módulos opcionais, que oferecem, por exemplo, integrações com Guava e Scala.
A implementação de todos esses exemplos e trechos de código pode ser encontrada emthe GitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.