Update na Tango

Olá pessoa!

Lembram da Tango? A biblioteca para programação funcional em C# que publiquei ano passado? Que tal um post sobre o update dela que rolou semana passada?

Caso você não faça ideia do que eu estou falando, você pode conferir este post, onde eu publiquei pela primeira vez sobre ela.

Mas vamos para a atualização! A primeira pergunta importante é: houve quebra de compatibilidade?

  • Definitivamente, não.

E o que mudou?

Todas as mudanças estão relacionadas com a struct Continuation de alguma forma. A ideia deste update foi dar uma repaginada nela e incluir alguns facilitadores.

Inclusão do método Finally

A primeira mudança relativamente simples foi a inclusão de um método chamado Finally. este método é utilizado geralmente no final da pipeline de processo para evitar duplicidade em algum código que precise ser executado independente do resultado do processo.

A ideia é semelhante ao Finally dos promises do JavaScript ou até mesmo do finally utilizado na instrução try-catch.

Continuation<string, int> continuation = 5;

continuation.Then(value => value + 4)
            .Then(value => value + 10)
            .Catch(fail => $"{fail} catched")
            .Finally(() => /*Execute o Código Aqui*/);

Note que o Finally não dá acesso ao valor armazenado no Continuation, isso porque devemos executar esta operação em casos onde não dependemos no valor. No entanto há uma sobrecarga do método que recebe por parâmetro um Either<TFail, TSuccess>, onde poderemos acessar o valor, mas sem modificar na estrutura original:

Continuation<string, int> continuation = 5;

continuation.Then(value => value + 4)
            .Then(value => value + 10)
            .Catch(fail => $"{fail} catched")
            .Finally(valores => /*Execute o Código Aqui*/); 

//valores = Either<string, int>

Essa inclusão também foi feita no modelo de operadores, neste caso o Finally ocupa o operador ==, segue o mesmo código, agora utilizando operadores:

Continuation<string, int> continuation = 5;
continuation
>  (value => value + 4)
>  (value => value + 10)
>= (fail => $"{fail} catched")
== (() => /*Execute o Código Aqui*/); 
== (valores => /*Execute o Código Aqui*/); 

//valores = Either<string, int>

Note que o operador também funciona com as duas sobrecargas.

Atenção Apesar de várias linguagens funcionais utilizarem por padrão a sintaxe com operadores esta não é a forma tradicional do C#, então ela possui limitações. Com as funções Then e Catch você pode alterar o tipo do resultado da pipeline utilizando generics: Then<int> ou Catch<string>, por exemplo. Não é possível realizar esta operação com os operadores.

Inclusão do método Merge

Além do método Finally a estrutura Continuation também recebeu o método Merge.

Já era possível realizar a operação de unir dois pipelines diferentes, mas o processo acabava sendo manual demais e bem trabalhoso, a partir de agora, basta utilizar o método e você terá um novo Continuation com a união dos dois anteriores, veja:

Continuation<string, int> continuation = 10;
Continuation<bool, double> continuation2 = 10.5;

int result =
    continuation.Merge(value => continuation2)
                .Then(values => values.Item1 + values.Item2)
                .Match(value => value, _ => 0);

//result = 20.5

O importante deste método é notar o que acontece depois dele. No Then que o sucede utilizamos a sintaxe Item1 e Item2, parece familiar?

Isso mesmo, estamos utilizando tuplas! Com o Merge, tanto a falha quanto o sucesso se tornam tuplas com os tipos dos dois continuations diferentes. No exemplo que fizemos, tínhamos os seguintes tipos: <string, int> e <bool, double>, então o novo Continuation gerado possui como falha: (string, bool) e como sucesso (int, double).

Como sabemos o estado (sucesso ou falha) do Continuation resultante? -Simples, o resultado só será considerado como sucesso, caso todos os processo envolvidos tenham sido completados com sucesso. Então o resultado será uma falha caso qualquer um dos dois esteja no estado de falha.

Com isso chegamos em um outro ponto, nos casos onde o resultado é bem sucedido, temos certeza de que o Continuation resultante conterá todos os valores. Mas isso não é verdade para o caso de falha, afinal mesmo se só um deles falhar, o resultado já será uma falha.

Para impedir acesso à informações que não existem, todos os dados da tupla de falha retornada pelo Merge são valores opcionais. Então, o retorno do exemplo será (Option<string>, Option<bool>).

Outro ponto importante deste método é que ele não possui uma versão com operadores.

ContinuationModule

A estrutra Continuation não possuía nenhum módulo, diferente dos tipos Either, Option e Collection. Por conta disso, este módulo foi criado provendo três métodos diferentes.

Resolve e Reject

Assim como as Promises do JavaScript, agora o módulo de Continuation provê uma forma rápida de criar pipelines nos dois estados: Success com Resolve e Fail com Reject.

Os dois métodos possuem duas sobrecargas diferentes, uma delas utilizando generics e a outra sem. Nos casos com generics você pode especificar o tipo do sucesso e da falha, caso contrário o tipo assumido será um Unit.

No caso do método Resolve o tipo Unit será atribuído para a falha e o tipo do parâmetro será definido como o tipo de sucesso, para o caso Reject isso acontece ao contrário, conforme código:

var continuation1 = ContinuationModule.Resolve(10); //Continuation<Unit, int>
var continuation2 = ContinuationModule.Resolve<bool, int>(10); //Continuation<bool, int>

var continuation3 = ContinuationModule.Reject("error"); //Continuation<string, Unit>
var continuation4 = ContinuationModule.Reject<string,int>("error"); //Continuation<string, int>

Estes métodos também são acessíveis através do extension method AsContinuation, conforme código:

var continuation1 = 10.AsContinuation<bool, int>(); 
//ContinuationModule.Resolve é chamado gerando um Continuation<bool, int>

var continuation2 = "fail".AsContinuation<string, bool>(); 
//ContinuationModule.Reject é chamado gerando um Continuation<string, bool>

No caso do método de extensão ele invocará internamente os métodos Resolve ou Reject de acordo com o tipo do valor que utilizou o método.

All

Por fim, temos o método All, este método deve ser utilizado para unir diferentes pipelines de execução. Na verdade, o método Merge citado anteriormente utiliza este método internamente.

A diferença do All para o Merge é que neste caso, você pode unir até 4 estruturas do tipo Continuation.

var continuation1 = ContinuationModule.Resolve(10);
var continuation2 = ContinuationModule.Resolve(true);
var continuation3 = ContinuationModule.Resolve("success");

(int, bool, string) result =
ContinuationModule.All(continuation1, continuation2, continuation3)
                    .Match(values => values, _ => (0, false, string.Empty));

//result = (10, true, "success")

Este método funciona da mesma maneira para dois, três ou quatro estruturas.

Por enquanto é isso pessoal, se você quiser utilizar este recurso basta realizar a atualização do seu pacote via Nuget ou baixar a versão no Github!

O que achou?

Alguma sugestão para posts futuros?

Gostou deste tipo de post?

Me conte nos comentários!

E Até mais!

Sempre vale lembrar que as informações e textos aqui no blog representam minha opinião pessoal, o que pode não ser igual à sua ou de qualquer outra pessoa, incluindo a empresa para qual eu trabalho. Portanto as publicações inseridas aqui estão relacionadas somente a mim.

Assine a Newsletter