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çãoNotSupportedException
.
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.
- Não há uma versão possível do pipe-backward
- É 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
eCatch
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.