A Necessary Compromise

Bringing modern abstractions into Delphi is usually an exercise in compromise.

There are two concerns to balance.

  • Idiomatic: Preserving the semantic clarity of an abstraction without unnecessary ceremony
  • Ergonomics: Making the type behave naturally within Object Pascal's own execution model

For example, this is overly complex:

Result := FirstStructuralToken.Match<Boolean>(
        function (const t: TToken): Boolean
        begin
          Result := t.TermKind = aKind;
        end,
        function: Boolean
        begin
            Result := False;
        end);

Whereas this is a better Delphi compromise:

Result := FirstStructuralToken.TryGetValue(t) and (t.TermKind = aKind);

Adapting Option Types to Delphi's Record Model

When implementing modern types such as TOption<T> in Delphi, the second compromise appears quickly: theoretical purity must give way to Delphi's actual operational model.

Option types are usually simple value abstractions. In languages such as Rust or Swift, they are modeled as algebraic data types with two clear constructors:

  • Some(value)
  • None

They are immutable and straightforward to reason about.

In Delphi, however, records do not map onto that model quite so directly.

The Record Result Slot

When a Delphi function returns a record, the compiler automatically allocates memory for the Result variable before the function body executes.

That means the result slot already exists before any factory method runs.

This has two implications:

  1. Record construction cannot always be treated as a pure factory operation
  2. Returning record values may introduce unnecessary copying if the implementation is not careful

In a language with algebraic types, a constructor would simply build a new value. In Delphi, the Result slot already exists, so construction often becomes initialization of an existing record.

This subtle difference shapes how TOption<T> must be designed.

A Simple Option Type

Conceptually, TOption<T> has two states:

  • Some(value)
  • None

In Delphi, however, a third internal state is useful: Unknown

It represents a record value that exists but has not yet been initialized as either Some or None.

The internal state machine therefore becomes:

Unknown → Some
Unknown → None

Once initialized, the state cannot change.

Preserving Immutability

To preserve immutability, the internal setters enforce a single-assignment rule:

procedure TOption<T>.SetSome(const aValue: T);
begin
  Ensure.IsTrue(fState = osUnknown, 'Option already initialized');
  
  fState := osSome;
  fValue := aValue;
end;

SetNone follows the same pattern.

These setters are only valid while the record remains in the Unknown state.

Once the state transitions to Some or None, further mutation is disallowed.

This preserves the immutability of the option type while still allowing efficient in-place initialization.

Factory Methods

Factory methods then become thin wrappers around these setters:

class function TOption<T>.Some(const aValue: T): TOption<T>;
begin
  Result.fState := osUnknown; 
  Result.SetSome(aValue);
end;
class function TOption<T>.None: TOption<T>;
begin
  Result.fState := osUnknown; 
  Result.SetNone;
end;

Because the Result slot already exists, these methods simply initialize it. This avoids unnecessary intermediate construction and keeps the implementation aligned with Delphi's Result semantics.

Making It Delphi-Friendly

A useful improvement comes from giving the Unknown state a clear semantic meaning.

Rather than exposing it, the implementation treats Unknown as equivalent to None when observed.

This means that a default-initialized TOption<T> behaves exactly like None.

In practice, this gives very natural Delphi ergonomics:

function FirstStructuralToken: TOption<TToken>;
begin
  // iterate tokens to find structural token
  
  if found then    
    Result.SetSome(token);
end;

If SetSome is never called, the option simply behaves as None.

No explicit None assignment is required.

The Payoff

The final design has several advantages:

  • Efficient record construction with no unnecessary copies
  • Immutability through single-assignment setters
  • Natural integration with Delphi's Result model
  • Safe default values
  • Clear Option semantics for callers

Internally, the type has three states. Externally, it behaves like the familiar two-state option abstraction. That is the contract.

A Pattern for Delphi Abstractions

This pattern illustrates an important principle when designing reusable abstractions in Delphi.

Rather than forcing the language to emulate another model exactly, it is often better to adapt the abstraction to Delphi's execution model while preserving its semantic guarantees.

In this case, the additional Unknown state is not a conceptual feature of the option type itself. It exists purely to reconcile option semantics with Delphi's record construction behavior.

The result is an abstraction that remains correct, efficient, and natural to use in Delphi code.