unit Myc.Data.Types.Tuple; interface uses System.Generics.Collections, System.SysUtils, System.Generics.Defaults, System.Hash, Myc.Data.Types; type // Custom comparer for TArray to be used as a dictionary key. TDataTupleTypeComparer = class(TInterfacedObject, IEqualityComparer>) public function Equals(const Left, Right: TArray): Boolean; reintroduce; function GetHashCode(const Value: TArray): Integer; reintroduce; end; // Implements the tuple data type. TImplDataTupleType = class(TInterfacedObject, IDataType, IDataTupleType) strict private // Class-level registry to cache and reuse tuple type definitions. class var FTupleTypeRegistry: TDictionary, IDataTupleType>; private FItemTypes: TArray; FEmptyValue: IDataTupleValue; // Cached instance for zero-field tuples function GetName: String; function GetKind: TDataKind; function CreateValue(const AItems: array of IDataValue): IDataTupleValue; function GetItemCount: Integer; function GetItemType(Idx: Integer): IDataType; public constructor Create(const AItemTypes: TArray); destructor Destroy; override; class constructor CreateClass; class destructor DestroyClass; class function GetInstance(const AItemTypes: array of IDataType): IDataTupleType; static; end; implementation uses System.SyncObjs, System.Rtti; type // Implements the tuple data value. TImplDataTupleValue = class(TInterfacedObject, IDataValue, IDataTupleValue) private FDataType: IDataTupleType; FItems: TArray; function GetDataType: IDataType; function GetAsString: string; function GetItemCount: Integer; function GetItem(Idx: Integer): IDataValue; public constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); end; // Optimized implementation for a tuple with 0 fields. TImplDataTupleValue0 = class(TInterfacedObject, IDataValue, IDataTupleValue) private FDataType: IDataTupleType; function GetDataType: IDataType; function GetAsString: string; function GetItemCount: Integer; function GetItem(Idx: Integer): IDataValue; public constructor Create(const ADataType: IDataTupleType); end; // Optimized implementation for a tuple with 1 field. TImplDataTupleValue1 = class(TInterfacedObject, IDataValue, IDataTupleValue) private FDataType: IDataTupleType; FItem0: IDataValue; function GetDataType: IDataType; function GetAsString: string; function GetItemCount: Integer; function GetItem(Idx: Integer): IDataValue; public constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); end; // Optimized implementation for a tuple with 2 fields. TImplDataTupleValue2 = class(TInterfacedObject, IDataValue, IDataTupleValue) private FDataType: IDataTupleType; FItem0, FItem1: IDataValue; function GetDataType: IDataType; function GetAsString: string; function GetItemCount: Integer; function GetItem(Idx: Integer): IDataValue; public constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); end; // Optimized implementation for a tuple with 3 fields. TImplDataTupleValue3 = class(TInterfacedObject, IDataValue, IDataTupleValue) private FDataType: IDataTupleType; FItem0, FItem1, FItem2: IDataValue; function GetDataType: IDataType; function GetAsString: string; function GetItemCount: Integer; function GetItem(Idx: Integer): IDataValue; public constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); end; // Optimized implementation for a tuple with 4 fields. TImplDataTupleValue4 = class(TInterfacedObject, IDataValue, IDataTupleValue) private FDataType: IDataTupleType; FItem0, FItem1, FItem2, FItem3: IDataValue; function GetDataType: IDataType; function GetAsString: string; function GetItemCount: Integer; function GetItem(Idx: Integer): IDataValue; public constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); end; { TDataTupleTypeComparer } function TDataTupleTypeComparer.Equals(const Left, Right: TArray): Boolean; var i: Integer; begin if (Length(Left) <> Length(Right)) then exit(False); for i := 0 to High(Left) do begin // Compare by interface reference. // It's safe because all IDataType instances are managed by GetInstance singletons. if (Left[i] <> Right[i]) then exit(False); end; Result := True; end; function TDataTupleTypeComparer.GetHashCode(const Value: TArray): Integer; var dataType: IDataType; begin Result := 0; for dataType in Value do begin // Use the hash of the interface's memory address. // This is safe and fast due to the singleton pattern for types. Result := Integer(PPointer(@dataType)^) xor Result; end; end; { TImplDataTupleType } class constructor TImplDataTupleType.CreateClass; begin // Initialize the global registry for tuple types. FTupleTypeRegistry := TDictionary, IDataTupleType>.Create(TDataTupleTypeComparer.Create); end; class destructor TImplDataTupleType.DestroyClass; begin FTupleTypeRegistry.Free; FTupleTypeRegistry := nil; end; constructor TImplDataTupleType.Create(const AItemTypes: TArray); begin inherited Create; FItemTypes := AItemTypes; // Pre-create the singleton value instance if this is a zero-field tuple. if (Length(FItemTypes) = 0) then FEmptyValue := TImplDataTupleValue0.Create(Self); end; destructor TImplDataTupleType.Destroy; begin inherited; end; function TImplDataTupleType.CreateValue(const AItems: array of IDataValue): IDataTupleValue; var i: Integer; begin if (Length(AItems) <> Length(FItemTypes)) then raise EArgumentException .CreateFmt('Invalid number of items for this tuple type. Expected %d, but got %d.', [Length(FItemTypes), Length(AItems)]); for i := 0 to High(AItems) do if not Assigned(AItems[i]) or (AItems[i].DataType <> FItemTypes[i]) then raise EArgumentException.CreateFmt( 'Invalid data type for item at index %d. Expected %s, but got %s.', [i, FItemTypes[i].Name, AItems[i].DataType.Name]); // Use optimized implementations for tuples with 0, 1, 2, 3, or 4 fields. case Length(FItemTypes) of 0: Result := FEmptyValue; // Return the pre-cached instance. 1: Result := TImplDataTupleValue1.Create(Self, AItems); 2: Result := TImplDataTupleValue2.Create(Self, AItems); 3: Result := TImplDataTupleValue3.Create(Self, AItems); 4: Result := TImplDataTupleValue4.Create(Self, AItems); else // Use the generic implementation for tuples with more than 4 fields. Result := TImplDataTupleValue.Create(Self, AItems); end; end; class function TImplDataTupleType.GetInstance(const AItemTypes: array of IDataType): IDataTupleType; var tItems: TArray; i: Integer; res: IDataTupleType; begin SetLength(tItems, Length(AItemTypes)); for i := 0 to High(AItemTypes) do tItems[i] := AItemTypes[i]; TMonitor.Enter(FTupleTypeRegistry); try if not FTupleTypeRegistry.TryGetValue(tItems, res) then begin res := TImplDataTupleType.Create(tItems); FTupleTypeRegistry.Add(tItems, res); end; finally TMonitor.Exit(FTupleTypeRegistry); end; Result := res; end; function TImplDataTupleType.GetItemCount: Integer; begin Result := Length(FItemTypes); end; function TImplDataTupleType.GetItemType(Idx: Integer): IDataType; begin Result := FItemTypes[Idx]; end; function TImplDataTupleType.GetName: String; var i: Integer; sb: TStringBuilder; begin sb := TStringBuilder.Create; try sb.Append('Tuple<'); for i := 0 to High(FItemTypes) do begin sb.Append(FItemTypes[i].Name); if (i < High(FItemTypes)) then sb.Append(', '); end; sb.Append('>'); Result := sb.ToString; finally sb.Free; end; end; function TImplDataTupleType.GetKind: TDataKind; begin Result := dkTuple; end; { TImplDataTupleValue (generic implementation for N > 4 fields) } constructor TImplDataTupleValue.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); var i: Integer; begin inherited Create; FDataType := ADataType; SetLength(FItems, Length(AItems)); for i := 0 to High(AItems) do FItems[i] := AItems[i]; end; function TImplDataTupleValue.GetDataType: IDataType; begin Result := FDataType; end; function TImplDataTupleValue.GetAsString: string; var sb: TStringBuilder; i: Integer; begin sb := TStringBuilder.Create; try sb.Append('('); for i := 0 to High(FItems) do begin sb.Append(FItems[i].AsString); if (i < High(FItems)) then sb.Append(', '); end; sb.Append(')'); Result := sb.ToString; finally sb.Free; end; end; function TImplDataTupleValue.GetItem(Idx: Integer): IDataValue; begin Result := FItems[Idx]; end; function TImplDataTupleValue.GetItemCount: Integer; begin Result := Length(FItems); end; { TImplDataTupleValue0 } constructor TImplDataTupleValue0.Create(const ADataType: IDataTupleType); begin inherited Create; FDataType := ADataType; end; function TImplDataTupleValue0.GetAsString: string; begin Result := '()'; end; function TImplDataTupleValue0.GetDataType: IDataType; begin Result := FDataType; end; function TImplDataTupleValue0.GetItem(Idx: Integer): IDataValue; begin raise EArgumentException.Create('Index out of bounds'); end; function TImplDataTupleValue0.GetItemCount: Integer; begin Result := 0; end; { TImplDataTupleValue1 } constructor TImplDataTupleValue1.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); begin inherited Create; FDataType := ADataType; FItem0 := AItems[0]; end; function TImplDataTupleValue1.GetAsString: string; begin Result := Format('(%s)', [FItem0.AsString]); end; function TImplDataTupleValue1.GetDataType: IDataType; begin Result := FDataType; end; function TImplDataTupleValue1.GetItem(Idx: Integer): IDataValue; begin case Idx of 0: Result := FItem0; else raise EArgumentException.Create('Index out of bounds'); end; end; function TImplDataTupleValue1.GetItemCount: Integer; begin Result := 1; end; { TImplDataTupleValue2 } constructor TImplDataTupleValue2.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); begin inherited Create; FDataType := ADataType; FItem0 := AItems[0]; FItem1 := AItems[1]; end; function TImplDataTupleValue2.GetAsString: string; var sb: TStringBuilder; begin sb := TStringBuilder.Create; try sb.Append('('); sb.Append(FItem0.AsString); sb.Append(', '); sb.Append(FItem1.AsString); sb.Append(')'); Result := sb.ToString; finally sb.Free; end; end; function TImplDataTupleValue2.GetDataType: IDataType; begin Result := FDataType; end; function TImplDataTupleValue2.GetItem(Idx: Integer): IDataValue; begin case Idx of 0: Result := FItem0; 1: Result := FItem1; else raise EArgumentException.Create('Index out of bounds'); end; end; function TImplDataTupleValue2.GetItemCount: Integer; begin Result := 2; end; { TImplDataTupleValue3 } constructor TImplDataTupleValue3.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); begin inherited Create; FDataType := ADataType; FItem0 := AItems[0]; FItem1 := AItems[1]; FItem2 := AItems[2]; end; function TImplDataTupleValue3.GetAsString: string; var sb: TStringBuilder; begin sb := TStringBuilder.Create; try sb.Append('('); sb.Append(FItem0.AsString); sb.Append(', '); sb.Append(FItem1.AsString); sb.Append(', '); sb.Append(FItem2.AsString); sb.Append(')'); Result := sb.ToString; finally sb.Free; end; end; function TImplDataTupleValue3.GetDataType: IDataType; begin Result := FDataType; end; function TImplDataTupleValue3.GetItem(Idx: Integer): IDataValue; begin case Idx of 0: Result := FItem0; 1: Result := FItem1; 2: Result := FItem2; else raise EArgumentException.Create('Index out of bounds'); end; end; function TImplDataTupleValue3.GetItemCount: Integer; begin Result := 3; end; { TImplDataTupleValue4 } constructor TImplDataTupleValue4.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue); begin inherited Create; FDataType := ADataType; FItem0 := AItems[0]; FItem1 := AItems[1]; FItem2 := AItems[2]; FItem3 := AItems[3]; end; function TImplDataTupleValue4.GetAsString: string; var sb: TStringBuilder; begin sb := TStringBuilder.Create; try sb.Append('('); sb.Append(FItem0.AsString); sb.Append(', '); sb.Append(FItem1.AsString); sb.Append(', '); sb.Append(FItem2.AsString); sb.Append(', '); sb.Append(FItem3.AsString); sb.Append(')'); Result := sb.ToString; finally sb.Free; end; end; function TImplDataTupleValue4.GetDataType: IDataType; begin Result := FDataType; end; function TImplDataTupleValue4.GetItem(Idx: Integer): IDataValue; begin case Idx of 0: Result := FItem0; 1: Result := FItem1; 2: Result := FItem2; 3: Result := FItem3; else raise EArgumentException.Create('Index out of bounds'); end; end; function TImplDataTupleValue4.GetItemCount: Integer; begin Result := 4; end; end.