Skip to main content

Railway Results — TResultPair<S, F>

Unit: ModernSyntax.ResultPair

TResultPair<S, F> implements the Railway-Oriented Programming pattern: every operation that can fail returns either a Success (S) or a Failure (F), making the error path explicit in the type signature and impossible to silently ignore.

Core concept

Input → [Operation] → Success(S)
↘ Failure(F)

The caller must explicitly branch on IsSuccess / IsFailure before accessing the inner value. There is no "current exception" hidden state.

Creating results

uses ModernSyntax.ResultPair;

function ParseInt(const AText: string): TResultPair<Integer, string>;
begin
var LValue: Integer;
if TryStrToInt(AText, LValue) then
Result := TResultPair<Integer, string>.Success(LValue)
else
Result := TResultPair<Integer, string>.Failure('Not a valid integer: ' + AText);
end;

Inspecting results

Method / PropertyDescription
IsSuccessTrue when the result holds a success value
IsFailureTrue when the result holds a failure value
ValueSuccessReturns the success value; behaviour when Failure is implementation-defined
ValueFailureReturns the failure value; behaviour when Success is implementation-defined
var LResult: TResultPair<Integer, string>;
begin
LResult := ParseInt('42');
if LResult.IsSuccess then
WriteLn('Parsed: ', LResult.ValueSuccess)
else
WriteLn('Error: ', LResult.ValueFailure);
end;

Chaining operations

TResultPair<S, F> supports chaining: register callbacks that are called only if the current state matches.

// ThenOf queues a success handler; ExceptOf queues a failure handler.
// Call Return to drain the queues.
LResult
.ThenOf(function(const ASuccess: Integer): TResultPair<Integer, string>
begin
Result := TResultPair<Integer, string>.Success(ASuccess * 2);
end)
.ExceptOf(function(const AFailure: string): TResultPair<Integer, string>
begin
WriteLn('Handling error: ', AFailure);
Result := TResultPair<Integer, string>.Failure(AFailure);
end)
.Return;

For immediate (non-queued) side-effect callbacks use Ok(ASuccessProc) and Fail(AFailureProc) — these execute the proc right away without touching the queue.

Exception wrappers

TResultPair includes exception types for converting between exception-based and result-based code:

TypeDescription
EFailureException<F>Raised by internal machinery when a failure must propagate as an exception
ESuccessException<S>Raised when a success value must propagate as an exception
ETypeIncompatibilityRaised on RTTI type mismatch in map/bind operations

TResultType enumeration

Internal state is tracked by TResultType:

ValueMeaning
rtNoneInitial/uninitialized state
rtSuccessResult holds a success value
rtFailureResult holds a failure value

Converting from TOption

TOption<T>.OkOr<F>(AFailure) converts an option to a result:

uses ModernSyntax.Option, ModernSyntax.ResultPair;

var
LName : TOption<string>;
LResult: TResultPair<string, string>;
begin
LName := TOption<string>.None;
LResult := LName.OkOr<string>('Name was not provided');
// LResult.IsFailure = True; LResult.ValueFailure = 'Name was not provided'
end;

Using with TMatch

TMatch<T>.Execute<R> and TMatch<T>.Execute both return TResultPair:

var LMatchResult: TResultPair<Boolean, string>;
begin
LMatchResult := TMatch<Integer>.Value(LValue)
.CaseGt(0, procedure begin WriteLn('Positive'); end)
.Execute;

if LMatchResult.IsFailure then
WriteLn('No case matched: ', LMatchResult.ValueFailure);
end;

TFuture (async results)

Async operations (TAsync.Await, TAsync.Run, TAsync.NoAwait) return TFuture — a lightweight analog to TResultPair for background tasks:

MethodDescription
IsOkTrue when the task completed successfully
IsErrTrue when the task raised an exception
Ok<T>Returns the typed result value
ErrReturns the error message string
uses ModernSyntax.Async;

var LFuture: TFuture;
begin
LFuture := Async(function: TValue
begin
Result := TValue.From<Integer>(42);
end).Await;

if LFuture.IsOk then
WriteLn('Result: ', LFuture.Ok<Integer>)
else
WriteLn('Error: ', LFuture.Err);
end;