Continuation<TFail, TSuccess>

Tango.Types.Continuation<TFail, TSuccess>

Esta classe representa valores que permitem encadeamento através de pipeline.

Instâncias de Continuation<TFail, TSuccess> encapsulam um valor que pode pertencer a um dos dois possíveis tipos: TFail ou TSuccess.

Neste ponto o tipo Continuation é muito semelhante ao tipo Either, mas as semelhanças acabam por aí. O propósito dos dois tipos é bastante distinto.

Enquanto o tipo Either encarrega-se de armazenar um dos dois valores possíveis, o tipo Continuation, além de também fazer isso, expõe uma série de métodos e operadores para criar um fluxo idiomático, sofisticado e poderoso para composição de seus métodos.

Devido à natureza deste tipo, não há uma demanda para compará-lo em conjunto com outro Continuation, por conta disso, este tipo não possui nenhuma implementação para o método Match2.

Ao invés disso, existem os métodos Then e Catch, que devem ser utilizados respectivamente para dar continuídade à composição quando a operação anterior foi bem sucedida e mal sucedida.

Através destes dois métodos a estrutura Continuation gera um pipeline entre duas ou mais funções, conectando sempre a saída de uma função como parâmetro da função seguinte.

Caso você tenha uma função f definida por A -> B e uma função g definida por B -> C, será possível criar uma nova função fg A -> C conectando o retorno de f como parâmetro de entrada para a função g.

Os valores Continuation possuem dois estados:

  • Sucesso - Quando contém um valor do tipo TSuccess;

  • Falha - Quando contém um valor do tipo TFail.

Propriedades

Nome

Tipo

Descrição

IsSuccess

bool

Retorna true quando o valor contido no tipo Continuation é do tipo definido por TSuccess. Representa o estado "Sucesso".

IsFail

bool

Retorna true quando o valor contido no tipo Continuation é do tipo definido por TFail. Representa o estado "Falha".

Construtores

Parâmetros

Retorno

Descrição

TSuccess success

Continuation<TFail, TSuccess>

Inicializa uma nova instância de um valor Continuation com o estado IsSuccess encapsulando o valor informado no parâmetro.

TFail fail

Continuation<TFail, TSuccess>

Inicializa uma nova instância de um valor Continuation com o estado IsFail encapsulando o valor informado no parâmetro.

Métodos

Nome

Parâmetros

Retorno

Descrição

Return

TSuccess success

Continuation<TFail, TSuccess>

Inicializa uma nova instância de um valor Continuation com o estado IsSuccess encapsulando o valor informado no parâmetro.

Return

TFail fail

Continuation<TFail, TSuccess>

Inicializa uma nova instância de um valor Continuation com o estado IsFail encapsulando o valor informado no parâmetro.

Match

Func<TSuccess,TResult> methodWhenSuccess

Func<TFail, TResult> methodWhenFail

TSuccess

Permite uma maneira de aplicar um método à um valor Continuation sem necessidade de checar o estado do valor.

Para isso são passados dois métodos por parâmetro. Estes métodos precisam retornar o mesmo tipo e cada um deles deve esperar um parâmetro diferente, dos tipos TFail e TSuccess. Eles serão chamados de acordo com o estado do valor Continuation (IsFail ou IsSuccess).

Match

Action<TSuccess> methodWhenSuccess

Action<TFail> methodWhenFail

Unit

Permite uma maneira de aplicar um método à um valor Continuation sem necessidade de checar o estado do valor.

Para isso são passados dois métodos por parâmetro. Estes métodos precisam não conter retorno (void) e cada um deles deve esperar um parâmetro diferente, dos tipos TFail e TSuccess. Eles serão chamados de acordo com o estado do valor Either (IsFail ou IsSuccess).

Then

Func<TSuccess, Continuation<TFail, TSuccess>> thenMethod

Continuation<TFail, TSuccess>

Permite uma maneira sofisticada e poderosa de compor um método através de diferentes funções. Quando o valor armazenado em Continuation estiver no estado IsSuccess o método informado no parâmetro é executado utilizando o valor armazenado no Continuation como parâmetro, caso contrário ocorre apenas um bypass até um método Catch ser encontrado.

Then

Func<TSuccess, Continuation<TFail, TNewSuccess>> thenMethod

Continuation<TFail, TNewSuccess>

Permite uma maneira sofisticada e poderosa de compor um método através de diferentes funções. Quando o valor armazenado em Continuation estiver no estado IsSuccess o método informado no parâmetro é executado utilizando o valor armazenado no Continuation como parâmetro, caso contrário ocorre apenas um bypass até um método Catch ser encontrado.

Then

Func<TParameter, TSuccess, Continuation<TFail, TNewSuccess>> thenMethod

TParameter parameter

Continuation<TFail, TNewSuccess>

Permite uma maneira sofisticada e poderosa de compor um método que contém através de diferentes funções. Quando o valor armazenado em Continuation estiver no estado IsSuccess o método informado no parâmetro é executado utilizando o valor armazenado no Continuation e o valor informado em parameter, como os dois parâmetros. Caso contrário ocorre apenas um bypass até um método Catch ser encontrado.

Catch

Func<TFail, Continuation<TFail, TSuccess>> catchMethod

Continuation<TFail, TSuccess>

Permite uma maneira sofisticada e poderosa de compor um método que contém através de diferentes funções. Quando o valor armazenado em Continuation estiver no estado IsFail o método informado no parâmetro é executado utilizando o valor armazenado no Continuation. Caso contrário ocorre apenas um bypass até um método Then ser encontrado.

Catch

Func<TFail, Continuation<TNewFail, TSuccess>> catchMethod

Continuation<TNewFail, TSuccess>

Permite uma maneira sofisticada e poderosa de compor um método que contém através de diferentes funções. Quando o valor armazenado em Continuation estiver no estado IsFail o método informado no parâmetro é executado utilizando o valor armazenado no Continuation. Caso contrário ocorre apenas um bypass até um método Then ser encontrado.

Finally

Action finallyMethod

Continuation<TFail, TSuccess>

Permite uma maneira de criar um código que deve ser executado no Continuation<TFail, TSuccess> independente de seu estado, com isso, evitando duplicar código nos métodos Then e Catch.

Finally

Action< Either<TFail,TSuccess> > finallyMethod

Continuation<TFail, TSuccess>

Permite uma maneira de criar um código que deve ser executado no Continuation<TFail, TSuccess> independente de seu estado, com isso, evitando duplicar código nos métodos Then e Catch.

Merge

Func<TSuccess, Continuation<TNewFail,TNewSuccess>> mergeMethod

Continuation<( Option<TFail>, Option<TNewFail>), (TSuccess, TNewSuccess)>

Permite uma maneira de unir dois diferentes Continuations em um único, onde os valores são agrupados em tuplas: Continuation<(TFail, TNewFail), (TSuccess, TNewSuccess)>.

Sobrecargas de operadores

Operador

Parâmetros

Retorno

Descrição

Cast implícito

TSuccess success

Continuation<TFail, TSuccess>

Inicializa uma nova instância de um valor Continuation com o estado IsSuccess encapsulando o valor informado no parâmetro.

Cast implícito

TFail fail

Continuation<TFail, TSuccess>

Inicializa uma nova instância de um valor Continuation com o estado IsFail encapsulando o valor informado no parâmetro.

Cast implícito

Continuation<TFail, TSuccess>

Option<TFail>

Permite a criação de um valor Option<TFail> através de um cast implícito de um valor Continuation.

Caso o valor Continuation esteja no estado IsFail será criado um valor opcional no estado IsSome.

Cast implícito

Continuation<TFail, TSuccess>

Option<TSuccess>

Permite a criação de um valor Option<TSuccess> através de um cast implícito de um valor Continuation.

Caso o valor Continuation esteja no estado IsSuccess será criado um valor opcional no estado IsSome.

Cast implícito

Either<TFail, TSuccess>

Continuation<TFail, TSuccess>

Permite a criação de um valor Continuation<TRight> através de um cast implícito de um valor Either.

Caso o valor Either esteja no estado IsLeft será criado um valor Continuation no estado IsFail, caso contrário, no estado IsSuccess.

Operador maior (>)

Continuation<TFail, TSuccess>

Func<TSuccess, Continuation<TFail, TSuccess>> thenMethod

Continuation<TFail, TSuccess>

Executa o método Then para realizar o pipeline através do operador.

Operador maior ou igual (>=)

Continuation<TFail, TSuccess>

Func<TSuccess, Continuation<TFail, TSuccess>> catchMethod

Continuation<TFail, TSuccess>

Executa o método Catch para realizar o pipeline através do operador.

Atenção

Por conta de obrigações da linguagem este tipo também implementa os operadores menor (<) e menor ou igual (<=). Mas caso qualquer um dos dois seja utilizado, a biblioteca irá lançar a exceção NotSupportedException.

Como Usar

Você pode criar um valor do tipo Continuation de várias formas diferentes, geralmente a criação de um Continuation indica que uma série de funções serão executadas a seguir.

Os modos de criação de um Continuation são bastante semelhantes à criação de um valor Either:

Criando valores Continuation

Você pode utilizar o construtor para criar valores contendo o estado IsFail ou IsSuccess de acordo com o parâmetro, conforme código:

Utilizando o construtor

Continuation<bool, int> valueWithRight = new Continuation<bool, int>(10); //-> IsSuccess
Continuation<bool, int> valueWithLeft = new Continuation<bool, int>(false); //-> IsFail

Você também pode utilizar o método estático Return.

Utilizando o método estático Return

Continuation<bool, int> valueWithRight = Continuation<bool, int>.Return(10); //-> IsSuccess
Continuation<bool, int> valueWithLeft = Continuation<bool, int>.Return(false); //-> IsFail

E como os tipos anteriores descritos nesta seção, também há as sobrecargas de cast implícito onde não é necessário se preocupar com nenhum tipo de sintaxe, basta criar o valor de um dos dois tipos TFail ou TSuccess declarado e a linguagem fará todo o trabalho.

Utilizando cast implícito

Continuation<bool, int> valueWithRight = 10;   //-> IsSuccess
Continuation<bool, int> valueWithLeft = false; //-> IsFail

Através deste cast implícito, você poderá gerar novos métodos para retornar o tipo Continuation em sua aplicação sem alterar nada no corpo especial da função, apenas indicando que a função retorna um valor deste tipo. Assim como no Either é possível informar duas instruções de return com tipos diferentes.

Veja este exemplo:

private Continuation<string, int> GetSquareIfEven(int value)
{
  if (value % 2 == 0)
    return value * value;
  else
    return "Odd";
}

Obtendo informação de um valor Continuation

Assim como nos valores Either e Option, a informação armazenada em um Continuation está encapsulada. Para obter a informação de um Continuation é necessário realizar um cast implícito para um valor opcional para TFail ou TSuccess ou utilizarmos o método Match.

Caso o tipo identificado pelo Option seja o tipo referente ao estado atual do valor Continuation será gerado um valor opcional no estado IsSome, caso contrário será gerado no estado IsNone.

Utilizando o cast implícito para Option

Continuation<bool, int> continuationValue = 10;
Option<int> optionValue = continuationValue;

//optionValue.IsSome = true
//optionValue.IsNone = false
Continuation<bool, int> continuationValue = 10;
Option<bool> optionValue = eitherValue;

//optionValue.IsSome = false
//optionValue.IsNone = true

Atenção

Mesmo realizando o cast implícito para o tipo correto, podem haver casos onde o valor opcional esteja no estado IsNone.

Esta situação ocorre quando o valor armazenado no tipo Continuation é igual ao valor default ou igual à null.

Continuation<bool, int> continuationValue = 0;
Option<int> optionValue = eitherValue;

//optionValue.IsSome = false
//optionValue.IsNone = true

O método Match disponível nesta estrutura funciona da mesma maneira que os métodos Match disponíveis em Option e Either.

Os métodos Match esperam dois métodos por parâmetro, estes métodos podem realizar transformações no valor Continuation, ou apenas retorná-los, conforme exemplos a seguir.

Utilizando Match com parâmetros nomeados

Continuation<bool, int> continuationValue = 10;
int value = continuationValue.Match(
        methodWhenSuccess: success => success,
        methodWhenFail: fail => 0);

//value = 10

O primeiro método será executado apenas se o valor Continuation estiver no estado IsSuccess, logo, este método recebe um valor do tipo que representa sucesso por parâmetro, int, no exemplo.

O segundo método recebe um valor do tipo que representa uma falha, bool, mas note que ambos precisam retornar valores do mesmo tipo. Portanto, foi utilizado o valor zero (0) para seguir o fluxo da aplicação caso o Continuation esteja no estado IsFail.

Não há necessidade de nomear os métodos, basta utilizá-los na ordem correta.

Utilizando Match

Continuation<bool, int> continuationValue = 10;
int value = continuationValue.Match(
         success => success,
         fail => 0);

//value = 10

Além disso, você também pode aplicar algum tipo de transformação no valor no momento de obtê-lo, como por exemplo, elevá-lo ao quadrado.

Continuation<bool, int> continuationValue = 10;
int value = continuationValue.Match(
        success => success * success,
        fail => 0);

//value = 100

Você também pode retornar o valor que precisar para o caso do estado ser IsFail, nos exemplos anteriores foi utilizado o valor zero, mas não há nenhuma obrigatoriedade nisso.

O método Match também não precisa retornar nenhum dos dois tipos do Continuation.

Utilizando Match para retornar um novo valor

Continuation<bool, int> continuationValue = 10;
string value = continuationValue.Match(
        success => success.ToString(),
        fail    => fail.ToString());

//value = "10"
Continuation<bool, int> continuationValue = true;
string value = continuationValue.Match(
        success => success.ToString(),
        fail    => fail.ToString());

//value = "true"

Não há a possibilidade de comparar dois valores Continuation ao mesmo tempo, este tipo não implementa o método Match2 por não fazer parte de seu contexto.

Criando Pipelines de Execução

O real valor do tipo Continuation está em sua capacidade de gerar métodos sofisticados e limpos através de operações em pipeline com os métodos Then e Catch.

Neste primeiro exemplo iremos apenas alterar um valor inteiro somando-o com cinco e depois com dez.

Utilizando o Then para alterar o valor

Continuation<bool, int> continuation = 5;
Option<int> optionResult = 
    continuation.Then(value => value + 5)
                .Then(value => value + 10);

//optionResult.IsSome = true
//optionResult.Some = 20

O objeto continuation inicialmente continha o valor 5, após executarmos o primeiro método Then o processamento é feito e o valor armazenado (5) é passado como parâmetro para a função anônima: value => value + 5.

Por fim, o resultado desta operação (10) é passado como parâmetro para a segunda função anônima: value => value + 10, produzindo o resultado final (20).

Quando o Then não é executado

Caso o continuation esteja no estado IsFail nenhum dos métodos Then é executado. Ocorre apenas um bypass do valor.

Continuation<bool, int> continuation = true;
Option<int> optionResult = 
    continuation.Then(value => value + 5)
                .Then(value => value + 10);

//optionResult.IsSome = false
//optionResult.IsNone = true

Neste caso, podemos obter o resultado utilizando um Option<bool para mapear a falha.

Utilizando cast implícito para obter a falha

Continuation<bool, int> continuation = true;
Option<bool> optionResult = 
    continuation.Then(value => value + 5)
                .Then(value => value + 10);

//optionResult.IsSome = true
//optionResult.Some = true

Um ponto importante a ser ressaltado é a possibilidade de uma das funções utilizadas em pipeline retornar o tipo referente à falha. Caso isso ocorra todos os métodos Then após ela serão ignorados.

Quando a falha ocorre no meio do caminho

Continuation<bool, int> continuation = 5;
Option<int> optionResult = 
    continuation.Then(value => value + 4)
                .Then(value => 
                      {
                        if( value % 2 == 0)
                            return value + 5;
                        else
                            return false;
                      })
                .Then(value => value + 10);

//optionResult.IsSome = false

No exemplo anterior, a falha ocorre somente no segundo Then, isso significa que o Then que o antecede executa normalmente, mas o Then que vem depois não será executado.

Para criar métodos que lidem com os erros, é necessário utilizar o método Catch.

Utilizando o Catch para tratar erros

Quando um Continuation está no valor IsFail os métodos Then não serão executados, eles apenas passarão o valor para o próximo método, até encontrar um método Catch.

Continuation<string, int> continuation = 5;
Option<string> optionResult = 
    continuation.Then(value => value + 4)
                .Then(value => 
                      {
                        if( value % 2 == 0)
                            return value + 5;
                        else
                            return "ERROR";
                      })
                .Then(value => value + 10)
                .Catch(fail => $"{fail} catched");

//optionResult.IsSome = true
//optionResult.Some = "ERROR catched"

Assim como o método Then também é possível executar diversos métodos Catch de forma encadeada.

Catches encadeados

Continuation<string, int> continuation = 5;
Option<string> optionResult = 
    continuation.Then(value => value + 4)
                .Then(value => 
                      {
                        if( value % 2 == 0)
                            return value + 5;
                        else
                            return "ERROR";
                      })
                .Then(value => value + 10)
                .Catch(fail => $"{fail} catched")
                .Catch(message => $"{message} again")
                .Catch(message => $"{message} and again");

//optionResult.IsSome = true
//optionResult.Some = "ERROR catched again and again"

Evite repetição de código com o Finally

Em alguns casos é necessário executar uma determinada ação independente do resultado contido no Continuation. Esta é a função principal do método Finally, funciona de maneira similar ao Then e ao Catch, mas neste caso, a função sempre é executada.

Outra diferença clara entre o Finally e os dois anteriores é a função recebida por parâmetro. Neste caso, você pode utilizar uma função que não recebe nenhum parâmetro.

Ela será executada e depois disso, o Continuation será retornado novamente para manter o fluxo contínuo.

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

ContinuationModule.Resolve(5)
                  .Then(value => value + 4)
                  .Then(value => value + 10)
                  .Catch(fail => $"{fail} catched")
                  .Finally(() => stopwatch.Stop());

Apesar de não ser obrigatório, o Finally geralmente é a última chamada do fluxo de um Continuation, além disso, é comum este método causar algum efeito colateral, portanto, seja cuidadoso.

Outra particularidade deste método é o fato de que ele não é capaz de modificar o valor armazenado no Continuation. Na verdade, é possível acessar o valor armazenado usando a sobrecarga que recebe um Action<Either<TFail, TSuccess>> como parâmetro.

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

ContinuationModule.Resolve<string, int>(5)
                  .Then(value => value + 4)
                  .Then(value => value + 10)
                  .Catch(fail => $"{fail} catched")
                  .Finally(values => Console.WriteLine(
                      values.Match(
                        number => number.ToString(), 
                        text => text)));

Assim como o Then e o Catch, o método Finally também permite encadeamentos.

Unindo dois Continuations diferentes em um novo

Esta não é uma operação comum, mas existem casos onde você precisa unir duas pipelines de valores Continuation diferentes para completar alguma determinada tarefa. Para facilitar este processo, você pode utilizar o método Merge.

A função recebida por parâmetro por este método recebe o próprio Continuation<TFail, TSuccess> por parâmetro e deve retornar um novo Continuation<TNewFail, TNewSuccess>. Depois disso, o retorno do Merge é criado agrupando os dois em um novo: Continuation<(Option<TFail>, Option<TNewFail>), (TSuccess, TNewSuccess)>

O continuation gerado pelo Merge estará no estado de Success somente quando os dois anteriores também estiverem, nos outros casos, o novo Continuation estará no estado Fail.

Você pode continuar seu fluxo normalmente, mas depois de chamar este método será necessário acessar as propriedades Item, afinal os valores foram agrupados em tuplas.

Continuation<bool, double> continuation2 = 28.5;
ContinuationModule.Resolve<string, int>(10)
                  .Then(value => value + 2)
                  .Merge(value => continuation2)
                  .Then(values => values.Item1 + values.Item2)
                  .Match(value => value, _ => 0);

Note que os tipos de falha do novo Continuation são tratados como valores opcionais, porque não há garantia de qual dos erros ocorreu.

Encadeando métodos com o operador de pipeline

A linguagem funcional da plataforma .NET, o F#, possui um operador para realizar pipelines, este operador é definido por: |> para pipe-foward e <| para reverse pipe ou pipe-backward.

Com eles podemos realizar operações na linguagem F# como estas:

let append1 string1 = string1 + ".append1"
let append2 string1 = string1 + ".append2"

let result1 = "abc" |> append1
printfn "\"abc\" |> append1 gives %A" result1

let result2 = "abc" 
              |> append1
              |> append2
printfn "result2: %A" result2

[1; 2; 3]
|> List.map (fun elem -> elem * 100)
|> List.rev
|> List.iter (fun elem -> printf "%d " elem)
printfn ""

Infelizmente não há como criar novos operadores no C# até a versão atual. No entanto, é possível sobrescrever os operadores existentes.

Pensando nisso, realizei a sobrescrita dos operadores > e >= para funcionarem de forma similar ao pipe-foward do F#.

Ao invés de realizarem as comparações de maior e maior ou igual, os operadores atuam recebendo como parâmetro um delegate Func idêntico aos utilizados nos métodos Then e Catch.

Por tanto é possível realizar as operações em pipeline substituindo as chamadas ao método Then pelo operador > e as chamadas ao método Catch pelo operador >=.

Utilizando o operador > para Then

Continuation<bool, int> continuation = 5;
Option<int> optionResult = 
    continuation 
    > (value => value + 5)
    > (value => value + 10)
    > (value => value + 10)


//optionResult.IsSome = true
//optionResult.Some = 30

Utilizando os operadores > e >= para Then e Catch

Continuation<string, int> continuation = 5;
Option<string> optionResult = 
    continuation 
    >  (value => value + 4)
    >  (value => 
       {
          if( value % 2 == 0)
              return value + 5;
          else
              return "ERROR";
       })
    >  (value => value + 10)
    >= (fail => $"{fail} catched")
    >= (message => $"{message} again")
    >= (message => $"{message} and again");

//optionResult.IsSome = true
//optionResult.Some = "ERROR catched again and again"

Atenção

Há uma limitação na utilização dos operadores. 1. Não há uma versão possível do pipe-backward 2. É possível utilizar o operador somente nos métodos que retornam um valor do mesmo tipo que seu parâmetro, diferente dos métodos Then e Catch não há como sobrecarregar os parâmetros com generics.

Quando o tipo do valor é alterado durante os métodos

Os dois métodos para realizar pipelines possuem sobrecargas para alterar o tipo do valor armazenado no Continuation, desta forma é possível transformar o valor ao longo da execução.

Alterando o tipo do Continuation durante as execuções

Continuation<object, int> continuation = 10;
Option<string> optionResult =
    continuation
    .Then<bool>(value => value % 2 == 0)
    .Then<string>(value => value ? "Even" : "Odd");

//optionResult.IsSome = true
//optionResult.Some = "Even"

Note que para alterar o tipo não é necessário utilizar a notação de generics Then<tipoDestino>, mas você pode explicitá-lo se quiser. O mesmo ocorre com o método Catch.

Alterando diversas vezes os tipos

Continuation<string, int> continuation = 1;

Option<double> optionResult =
    continuation
    .Then<bool>(value =>
    {
        if (value % 2 == 0)
            return true;
        else
            return "Life, the Universe and Everything";
    })
    .Catch<double>(message => 42.0);

//optionResult.IsSome = true
//optionResult.Some = 42.0

Quando é necessário unir os resultados em um único tipo

Quando é necessário unificar as duas possibilidades de valores em um Continuation é sugerido utilizar o método Match após todas as execuções.

Continuation<bool, int> continuation = 5;
int integerResult = 
    continuation.Then(value => value + 4)
                .Then(value => 
                      {
                        if( value % 2 == 0)
                            return value + 5;
                        else
                            return false;
                      })
                .Then(value => value + 10)
                .Match(success => success,
                       fail    => 0);

//integerResult  = 0

Assim como nos outros tipos você pode utilizar o Match para retornar qualquer tipo, desde que ambos os métodos o retornem.

Last updated