A TMaybe Class for Spring4D

Issue #43 wontfix
Nick Hodges created an issue

I humbly offer the following two classes for inclusion in the Spring framework. I am aware that this can very likely be improved by people smarter than me.

I split it into two classes because I couldn't find a way to make a single class work for both primitives and classes.

The idea is that in order to really encapsulate something, you have to have a "safe" API. The TMaybe classes enable you to always return *something", instead of nil or empty or "nothing", The idea is that the return value will have either one valid item or the default value for the type.

It helps ensure that nil is never returned as a function result.

This comes from Mark Seemann, or at least his PluralSight course was the first place I have ever seen it used.

Thoughts?

unit Spring.Maybe;

interface

uses
      Spring
    , Spring.Collections
    ;

type
  TPrimitiveMaybe<T> = class(TInterfacedObject, IEnumerable<T>)
  private
    FList: IList<T>;
    function GetValue: T;
    property Item: IList<T> read FList implements IEnumerable<T>;
  public
    constructor Create; overload;
    constructor Create(aT: T); overload;
    property Value: T read GetValue;
  end;

  TClassMaybe<T: class, constructor> = class(TInterfacedObject, IEnumerable<T>)
  private
    FList: IList<T>;
    function GetValue: T;
    property Item: IList<T> read FList implements IEnumerable<T>;
  public
    constructor Create; overload;
    constructor Create(aT: T); overload;
    property Value: T read GetValue;
  end;

implementation

{ TBaseTypeMaybe<T> }

constructor TPrimitiveMaybe<T>.Create;
begin
  inherited Create;
  FList := TCollections.CreateList<T>;
end;

constructor TPrimitiveMaybe<T>.Create(aT: T);
begin
  inherited Create;
  FList := TCollections.CreateList<T>;
  FList.Add(aT);
end;

function TPrimitiveMaybe<T>.GetValue: T;
begin
  Result := FList.SingleOrDefault
end;

{ TClassMaybe<T> }

constructor TClassMaybe<T>.Create;
begin
  inherited Create;
  FList := TCollections.CreateList<T>;
  FList.Add(T.Create);
end;

constructor TClassMaybe<T>.Create(aT: T);
begin
 Guard.CheckNotNull(aT, 'aT');
 inherited Create;
 FList := TCollections.CreateList<T>;
 FList.Add(aT);
end;

function TClassMaybe<T>.GetValue: T;
begin
  Result := FList.SingleOrDefault;
end;

end.

Comments (8)

  1. Stefan Glienke repo owner

    Can you also post some code that would show it's use? Because after reading a couple of articles about the maybe monad in other languages I feel that the code would not get any cleaner but just more obscure and less understandable. Without proper extension methods in Delphi this would be even worse I think.

  2. Nick Hodges reporter

    The idea is to provide a type that can never return nil. Now that I think about it, that makes the TPrimitiveClass<T> almost useless, because none of Delphi's primitives can be nil.

    TClassMaybe will only work for classes with default, parameterless constructors. Not all Delphi classes have that.

    The idea is that instead of

    TMyClass = class
      function GetMyWidget: TMyWidget;
    end;
    

    you'd have

    TMyClass = class
      function GetMyWidget: TClassMaybe<TMyWidget>;
    end;
    

    Where you'd guarantee that the return value of GetMyWidget would never be nil. But the class would have to have the default constructor, etc.

    In the end, after further consideration, consider this withdrawn. It is valuable in the .Net and Java worlds where, say, a string can be null, but in Delphi, it just doesn't work after all.

  3. Stefan Glienke repo owner

    It's also more valuable in languages that have dynamic typing and embrace functional programming more than Delphi does. Also when we think about being nil safe Delphi offers something that C# does not (without any judgement if that is good or bad). You can call a method on a nil reference whereas in C# you get an NPE.

    So in fact you can write code like this:

    var
      street: string;
    begin
      street := person.Address.Street
    

    where person or its Address property are nil by writing getters like these:

    function TPerson.GetAddress: TAddress;
    begin
      if Assigned(Self) then
        Result := fAddress
      else
        Result := nil;
    end;
    
    function TAddress.GetStreet: string;
    begin
      if Assigned(Self) then
        Result := fStreet
      else
        Result := '';
    end;
    

    P.S.: Fun fact: C# 6.0 will have this - called Null propagation

  4. Nick Hodges reporter

    Yeah -- the idea was to ensure that a function (query) returns something and not nil. But since Delphi isn't rooted, it already defaults most types and thus makes TMaybe redundant.

    Interesting thought experiment at least. :-)

  5. Stefan Glienke repo owner

    From a functional perspective it is correct - however nullable types as in C# or Spring4D are to add the state of null to a value type - hence the difference to the option type.

    Reference types already have the state of null/nil (which causes other problems as we all know). These problems usually are being solved via option types in functional programming because they offer pattern matching and checking if something is some or none in a rather convenient way.

  6. Log in to comment