Record Unpacking
I was looking into implementing tuples, when I realized that tuple types would be basically just syntactic sugar for anonymous records, which goes against the philosophy of Pascal of having to declare types before usage. But from those works I still got something out I thought might still be useful, and that is record unpacking.
Basically this MR introduces two new syntactic constructs:
- Tuples
(item1, item2, item3) - Pack/Unpack operations over records (
...myrecord) and (right now just static) arrays...myarray
Now what I've built is allow to use those in assignments for one another. So first example is packing a record with a tuple:
type
TTestRec = record
first: Integer;
second: String;
end;
var
r: TTestRec;
begin
...r := (42, 'Hello World');
end.
Here r will be unpacked to each of it's components, which then get each of the values from the tuple assigned.
More interesting is the other way around, which is unpacking:
var
i: Integer;
s: String;
begin
(i, s) := ...r;
end;
Here r will be unpacked into i and s. Because of the implementation also the following works:
var
i: Integer;
s: String;
begin
(i, s) := (42, 'Hello World');
end;
If that is actually desirable is a different question, for now we get this "for free".
But sometimes you might only be interested in a few values, for this the MR contains two options, first partial unpacking:
type
TTestRec = record
first: Integer;
second: String;
third: Double;
end;
var
r: TTestRec;
i: Integer;
s: String;
begin
(i, s) := ...r; // Only unpacks as many values as the left hand side can match
end.
Or if you need to skip something in between you can use nil:
var
r: TTestRec;
i: Integer;
d: Double;
begin
(i, nil, d) := ...r; // skips the unpacking of the second field
end.
A few extra things, you can use this to convert between equivalent but different record types:
var
p1: Tuples.specialize TPair<String, Integer>; // TPair from tuples unit
p2: Generics.Collections.specialize TPair<String, Integer>; // TPair from generics.collections unit
begin
...p1 := ...p2;
end;
This also does the usual assignment type conversions, so you could use this to convert ansistring records into widestring records. But you can also nest things:
var
i: Integer;
r: TTestRec;
r2: TBiggerRec;
begin
(i, ...r) := ...r2; // Unpack first field into i, remaining fields into r
end;
All that said here about records also works with static arrays.
What else to do
Now this is just a first implementation up for discussion. Other things one could imagine:
- Use in function calls
procedure Foo(i: Integer; s: String);
...
var
p: specialize TPair<Integer, String>;
begin
Foo(...p);
end;
- Allowing inside casts for record construction:
TTestRec(42, 'Hello World', 3.14);
- Usage for record var parameters:
procedure Foo(var p: specialize TPair<Integer, String>);
...
var
i: Integer;
s: String;
begin
Foo(...(i, s)); // Foo will write to i and s
end;
- Matching nested record types:
TTestRec = record
first: Integer;
r: record
nestedSecod: String;
nestedThird: Double;
end;
end;
...
var
i: Integer;
s: String;
begin
(i, ...(s, d)) := myRec;
end;
- Pattern matching:
type
TTestRecord = record
case kind: (rkFirst, rkSecond) of
rkFirst: (i: Integer);
rkSecond: (d: Double);
end;
var
i: Integer;
d: Double;
begin
case rec of
(rkFirst, i): WriteLn('Its an Int: ', i);
(rkSecond, d): WriteLn('Its a Double: ', d);
end;
end;
Just to give a few ideas. But I think even without those, that the assignment packing and unpacking alone are already super useful especially when used with generic records like from the tuples unit.