Last active
July 1, 2019 23:31
-
-
Save Akira13641/0da9f0a87929b68983708c68ff27b07a to your computer and use it in GitHub Desktop.
Made this for fun based on a C++ proposal I read about a while ago.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
unit Expected; | |
// The "mode Delphi" directive below turns on Free Pascal's Delphi-syntax compatibility mode, | |
// needed here to declare and write the generics in the style I have. | |
{$mode Delphi}{$H+}{$J-} | |
{$ImplicitExceptions Off} | |
{$Assertions On} | |
interface | |
// ↓↓↓ Correctly spelled, I promise. TypInfo is a unit, TypeInfo is the name of a function in that unit. | |
uses TypInfo; | |
type | |
TExpected<T, E> = record | |
strict private type | |
PT = ^T; | |
PE = ^E; | |
// ↓↓↓ A type alias for the sake of convenience, not strictly necessary | |
SelfType = TExpected<T, E>; | |
TValueRec = record Val: T; end; | |
TErrorRec = record Err: E; end; | |
TMapFunc = function(var Val: T): SelfType; | |
TMapProc = procedure(var Val: T); | |
strict private class var | |
// these are type-level "statics", basically | |
ValueHaltString, ErrorHaltString: ShortString; | |
strict private | |
class constructor Init; | |
procedure HaltFromValue; inline; | |
procedure HaltFromError; inline; | |
function GetValue: PT; inline; | |
function GetError: PE; inline; | |
private | |
class operator Implicit(constref From: T): SelfType; inline; | |
class operator Implicit(constref From: E): SelfType; inline; | |
class operator Implicit(constref From: SelfType): Boolean; inline; | |
class operator Not(constref From: SelfType): Boolean; inline; | |
public | |
function AndThen(const F: TMapFunc): SelfType; inline; | |
procedure Map(const P: TMapProc); inline; | |
property AsValue: PT read GetValue; | |
property AsError: PE read GetError; | |
strict private | |
case Positive: Boolean of | |
True: (Value: TValueRec); | |
False: (Error: TErrorRec); | |
end; | |
implementation | |
// TExpected is a record and not a class obviously, but what "class constructor" means below is that the constructor applies | |
// to the type as a whole, not instances of it. It will be called automatically at program startup exactly once for each unique | |
// specialization of TExpected. There's no heap allocation in this file or the next one at all BTW, despite what it might look like. | |
class constructor TExpected<T, E>.Init; | |
const | |
ValuePartA: ShortString = 'Attempted to access the error value of a positive-state "TExpected<'; | |
ErrorPartA: ShortString = 'Attempted to access the success value of a negative-state "TExpected<'; | |
PartC: ShortString = ', '; | |
PartE: ShortString = '>" instance!'; | |
var VP, EP: PTypeInfo; | |
begin | |
VP := PTypeInfo(TypeInfo(T)); | |
EP := PTypeInfo(TypeInfo(E)); | |
ValueHaltString := Concat(ValuePartA, VP^.Name, PartC, EP^.Name, PartE); | |
ErrorHaltString := Concat(ErrorPartA, VP^.Name, PartC, EP^.Name, PartE); | |
end; | |
procedure TExpected<T, E>.HaltFromValue; | |
begin | |
Assert(False, ValueHaltString); | |
end; | |
procedure TExpected<T, E>.HaltFromError; | |
begin | |
Assert(False, ErrorHaltString); | |
end; | |
function TExpected<T, E>.GetValue: PT; | |
begin | |
if Positive then Exit(@Value.Val); | |
HaltFromValue(); | |
end; | |
function TExpected<T, E>.GetError: PE; | |
begin | |
if not Positive then Exit(@Error.Err); | |
HaltFromError(); | |
end; | |
class operator TExpected<T, E>.Implicit(constref From: T): SelfType; | |
begin | |
with Result do begin | |
Positive := True; | |
Value.Val := From; | |
end; | |
end; | |
class operator TExpected<T, E>.Implicit(constref From: E): SelfType; | |
begin | |
with Result do begin | |
Positive := False; | |
Error.Err := From; | |
end; | |
end; | |
class operator TExpected<T, E>.Implicit(constref From: SelfType): Boolean; | |
begin | |
Result := From.Positive; | |
end; | |
class operator TExpected<T, E>.Not(constref From: SelfType): Boolean; | |
begin | |
Result := not From.Positive; | |
end; | |
function TExpected<T, E>.AndThen(const F: TMapFunc): SelfType; | |
begin | |
if Positive then Exit(F(Value.Val)); | |
HaltFromError(); | |
end; | |
procedure TExpected<T, E>.Map(const P: TMapProc); | |
begin | |
if Positive then P(Value.Val); | |
end; | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
program TestExpected; | |
{$mode Delphi}{$H+}{$J-} | |
{$ImplicitExceptions Off} | |
{$Assertions On} | |
uses Expected; | |
type IntStringResult = TExpected<SizeUInt, ShortString>; | |
function ReturnExpected(const B: Boolean; | |
const I: SizeUInt; | |
const S: ShortString): IntStringResult; inline; | |
begin | |
if B then | |
Result := I | |
else | |
Result := S; | |
end; | |
function TestFunc(var Val: SizeUInt): IntStringResult; | |
begin | |
Val *= Val; | |
Result := Val; | |
end; | |
procedure TestProc(var Val: SizeUInt); | |
begin | |
Val := Val div 2; | |
end; | |
var Exp: IntStringResult; | |
begin | |
Exp := ReturnExpected(True, 5, ''); | |
if Exp then WriteLn('True'); | |
WriteLn(Exp.AsValue^); | |
Exp := ReturnExpected(False, 0, 'Hello!'); | |
if not Exp then WriteLn('False'); | |
WriteLn(Exp.AsError^); | |
Exp := ReturnExpected(True, 10, ''); | |
with Exp do begin | |
WriteLn( | |
AndThen(TestFunc) | |
.AndThen(TestFunc) | |
.AndThen(TestFunc) | |
.AsValue^ | |
); | |
Map(TestProc); | |
WriteLn(AsValue^); | |
end; | |
WriteLn(ReturnExpected(False, 12, 'Hello!') | |
.AndThen(TestFunc) | |
.AsValue^); | |
WriteLn('Not supposed to get this far!'); | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment