Continuation<TFail, TSuccess>

Tango.Types.Continuation<TFail, TSuccess>

This type represents a value that allows chainable operations with methods and operations.

Instances of Continuation<TFail, TSuccess> contains an instance of TSuccess or TFail similar to Either values.

However, the Either type focus on encapsulate one of two possible values, the Continuation is more focused to creates a mechanism of a sophisticated, powerful and idiomatic flow to compose your methods.

Because of the nature of this type, this type doesn't implements the Match2 method. Instead of it, this class do have the powerful Then and Catch methods. These methods allows to create a continuous flow to successful and unsucessful operations.

Through these two methods the Continuation struct can generate a pipeline with two or more functions by connecting the output of one function as a parameter to the next function in the pipeline flow.

If you have a f function defined by A -> B and a g function defined by B -> C, you can create a new fg function as A -> C connecting the returns of f as a g input.

The Continuation values can assume two different states:

  • Success - When it contains a TSuccess value;

  • Fail - When it contains a TFail value.

Properties

Name

Type

Description

IsSuccess

bool

Returns true when the result value is a TSuccess value. Otherwise, returns false.

IsFail

bool

Returns true when the result value is a TFail value. Otherwise, returns false.

Constructors

Parâmetros

Retorno

Descrição

TSuccess success

Continuation<TFail, TSuccess>

Initialize a new instance of Continuation<TFail, TSuccess> with a TSuccess value.

TFail fail

Continuation<TFail, TSuccess>

Initialize a new instance of Continuation<TFail, TSuccess> with a TFail value.

Methods

Name

Parameters

Returns

Description

Return

TSuccess success

Continuation<TFail, TSuccess>

Initialize a new instance of Continuation<TFail, TSuccess> with a TSuccess value.

Return

TFail fail

Continuation<TFail, TSuccess>

Initialize a new instance of Continuation<TFail, TSuccess> with a TFail value.

Match

Func<TSuccess,TResult> methodWhenSuccess

Func<TFail, TResult> methodWhenFail

TSuccess

This allows a sophisticated way to apply some method for Continuation<TFail, TSuccess> values without having to check for the existence of a TFail or TSuccess value.

Match

Action<TSuccess> methodWhenSuccess

Action<TFail> methodWhenFail

Unit

This allows a sophisticated way to apply some method for Continuation<TFail, TSuccess> values without having to check for the existence of a TFail or TSuccess value.

Then

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

Continuation<TFail, TSuccess>

This allows a sophisticated and powerful way to apply some method in order to compose an operation with different functions.

When the current Continuation<TFail, TSuccess> IsSuccess the thenMethod is applied. Otherwise, returns itself until encounter a Catch function.

Then

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

Continuation<TFail, TNewSuccess>

This allows a sophisticated and powerful way to apply some method in order to compose an operation with different functions.

When the current Continuation<TFail, TSuccess> IsSuccess the thenMethod is applied. Otherwise, returns itself until encounter a Catch function.

Then

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

TParameter parameter

Continuation<TFail, TNewSuccess>

This allows a sophisticated and powerful way to apply some method in order to compose an operation with different functions.

When the current Continuation<TFail, TSuccess> IsSuccess the thenMethod is applied. Otherwise, returns itself until encounter a Catch function.

Catch

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

Continuation<TFail, TSuccess>

This allows a sophisticated and powerful way to apply some method in order to compose an operation with different functions.

When the current Continuation<TFail, TSuccess> IsFail the catchMethod is applied. Otherwise, returns itself until encounter a Then function.

Catch

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

Continuation<TNewFail, TSuccess>

This allows a sophisticated and powerful way to apply some method in order to compose an operation with different functions.

When the current Continuation<TFail, TSuccess> IsFail the catchMethod is applied. Otherwise, returns itself until encounter a Then function.

Finally

Action finallyMethod

Continuation<TFail, TSuccess>

This provides a way for code that must be executed once the Continuation<TFail, TSuccess> has been dealt with to be run whether the was fulfilled successfully or failed.

This lets you avoid duplicating code in both the Then and Catch methods.

Finally

Action< Either<TFail,TSuccess> > finallyMethod

Continuation<TFail, TSuccess>

This provides a way for code that must be executed once the Continuation<TFail, TSuccess> has been dealt with to be run whether the was fulfilled successfully or failed.

This lets you avoid duplicating code in both the Then and Catch methods.

Merge

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

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

This allows a powerful way to merge two different Continuations in a single grouped Continuation<(TFail, TNewFail), (TSuccess, TNewSuccess)>.

Operators Overload

Operator

Parameters

Returns

Description

Implicit cast

TSuccess success

Continuation<TFail, TSuccess>

Initialize a new instance of Continuation<TFail, TSuccess> with a TSuccess value.

Implicit cast

TFail fail

Continuation<TFail, TSuccess>

Initialize a new instance of Continuation<TFail, TSuccess> with a TFail value.

Implicit cast

Continuation<TFail, TSuccess>

Option<TFail>

Creates an Option<TFail> of TFail value by Success property of the Continuation<TFail, TSuccess> value.

Implicit cast

Continuation<TFail, TSuccess>

Option<TSuccess>

Creates an Option<TSuccess> of TSuccess value by Success property of the Continuation<TFail, TSuccess> value.

Implicit cast

Either<TFail, TSuccess>

Continuation<TFail, TSuccess>

Creates a Continuation<TFail, TSuccess> from an Either<TFail, TSuccess> value.

Greater than (>)

Continuation<TFail, TSuccess>

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

Continuation<TFail, TSuccess>

Executes the Then method to create a pipeline flow.

Greater or equal (>=)

Continuation<TFail, TSuccess>

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

Continuation<TFail, TSuccess>

Executes the Catch method to create a pipeline flow.

WARNING

Because of languages limitations the Continuation type also implements an overload to (<) and (<=). But if any of these operators raises a new NotSupportedException.

Usage

You can use a Continuation value in so many different ways, usually the creation of a Continuation value indicates that a series of functions will be executed as a composition.

Creating a continuation value

You can use the constructor to create option values in IsFail or IsSuccess states, according its parameter.

Constructors

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

You can use the Return static method as well.

The Return static method

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

Finally the simplest version: implict cast. Because of this feature you don't need to worry about any kind of syntax, you just create the value as regular types.

Implicit cast

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

Through this implicit cast you can creates new option values by using regular return commands, in this particular case is very common to use two different return commands with different types.

See the code bellow:

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

It can looks weird at first, by having two returns with two different types, but actually both returns will creates a new Continuation value. But instead of deal with it, Tango pass this responsibility to C#.

Getting value from an continuation

Like the other containers types it is necessary to use the Match method to get the encapsulated value of an Continuation type.

In this type you can also get the value by an implicit cast to an Option of either TFail or TRight. In this particular case the option value will receive one of these two types and creates an value according to the Continuation value.

Implict cast to an 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

WARNING

Even when the implicit cast was made for the correct type the option value can receive an None value, because the restrictions of option value.

This case will occurs when the Either encapsulated value is equals to null or its default.

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

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

You can also uses the Match method. This method receive two different functions as parameters, one for each case: Success and Fail states

Match with named parameters

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

//value = 10

The first method is executed when the continuation IsSuccess, because of it, this method receive a TSuccess value as parameter (int in this example).

The second method is executed when the continuation IsFail, because of it, this method receive a TFail value as parameter (bool in this example).

Just like Either types does.

Finally, these two functions are just regular parameters, so, you can omit the its names.

Match

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

//value = 10

Besides that, you can also apply some transformation in value before got it, like square the value:

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

//value = 100

You can also returns any value in IsFail case, in the previous examples were used the zero value, but it isn't obrigatory.

The Match method can creates any type of value, even types that are not defined as TFail or TSuccess.

Match to create a new result

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"

There's no way to compare two different Continuation values simultaneously.

Creating Pipelines

The main value of the Continuation type is its capacity to creates clean and sophisticated methods in a pipeline flow by using Then and Catch methods.

You can encapsulate an integer value in a Continuation type and increasing this value in a pipeline flow, as showed by the following example.

Using Then to increase a value

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

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

Initially, the Continuation structure contained the value 5, after the first Then method the value was increased by 5. It occurs because the encapsulated value is passed as a parameter to the anonymous function described by value => value + 5.

Finally, the result of the operation described by the first function (10) is passed to the second anonymous function: value => value + 10 creating the final result (20).

When Then isn't executed

When the Continuation state IsFail neither of the sThen methods are executed, it performs just a bypass.

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

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

In this particular case, you can use the implicit cast feature to get an Option<bool> to deal with the fail of the process.

Implicit cast to get the fail result

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

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

It is extremely important to notice that there's a real situation where the fail occurs inside the pipeline flow.

When it occurs, all of the Then methods described bellow the fail function will be ignored.

When the fail occurs inside the pipeline flow

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

The previous example shows a function that will creates a fail in the second Then method. Because of this, the third Then method will performs just a bypass.

You can use the Catch method to create methods to deal with Fail values.

Using Catch to deal with fails

When a Continuation value contains a TFail value it will be ignored by the Then methods, that's the main point about Catch methods, it a similar method, but in this case, it will execute when the value is TFail and will performs a bypass when the value is `TSuccess´.

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"

Like the Then methods, Catch methods are also chainables.

Chained Catches

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"

Avoid repeat yourself by using Finally

Sometimes you need to perform an action no matter the Continuation value result contains, either a TFail or TSuccess value. That's the main point about Finally methods, it is similar to Then and Catch, but in this case, it always will execute.

Other notorius difference between Finally and the other two methods, is your function parameter. In this case, you can use an Action without any argument. It will be executed and the Continuation will be returned to keep the Continuation flow.

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

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

Usually the Finally method will be the last method of a Continuation flow and cause a side effect, so, be careful and keep your eye on it.

Another major difference between this method and the two previous ones, is the fact that you're not able to modify the Continuation value. Actually you can access the values by using an overload of Finally that receives an Action<Either<TFail, TSuccess>> type as argument.

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)));

Like Then and Catch methods, Finally methods are also chainables.

Merging two pipelines in a single one

This isn't a common operation, but sometimes you need to merge two different Continuation pipelines to complete your use case. In order to make it easy, you can use the Merge method.

The argument function of this method receive the entire Continuation<TFail, TSuccess> itself as argument and need to return an brand new Continuation<TNewFail, TNewSuccess>. The Merge return is a new grouped Continuation<(Option<TFail>, Option<TNewFail>), (TSuccess, TNewSuccess)>.

This new Continuation will be in Success state only when the two previous ones are also in this state, otherwise it will be in Fail state.

You can continue your pipeline, but now, you need to access the Item properties, since the values were grouped.

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);

The new Continuation fail types are treated as Option values, because there's no guarantee of wich error was occured.

Chaining methods with pipeline operator

The .NET functional programming language, F#, has an operator to performs these pipeline flows.

This operator are defined by |> as pipe-foward and <| as reverse pipe or pipe-backward.

With these operators you can perform an elegant pipeline flow like this:

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)

Unfortunatelly there's no way to create new operators for your C# code. However, is totally possible to overload the current operators.

With this mindset, Tango created some overloads to > and >= operators to work as pipelines, similar to F# syntax.

Instead of performs a comparison as its default behavior, these operations works as Then and Catch methods.

Therefore, is fully possible to creates pipelines replacing Then for > and Catch for >=.

Replacing Then by >

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


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

Replacing Then and Catch by > and >=

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"

WARNING

There are some limitations by usign operators: 1. Different from F#, is not possible to performs a reverse pipe; 2. It's possible to replace the Then and Catch methods with the operators only when the resulting value is the same type as the current Continuation. There's no way to overload an operator with generics.

When you want to change the Continuation types

Both Then and Catch methods contains overloads that implements the possibility to create a new Continuation type, this way is possible to change the TSuccess and TFail throughout the pipeline.

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"

It is important to say that isn't necessary using the generics notation: Then<targetType>, but you can use it, if you want to.

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

Get the final result

Usually, after the pipeline you'll need to get the final value, or handle the eventually failures at least.

You can use the Match method after all Then and Catch methods.

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

Last updated