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:

  1. Tuples (item1, item2, item3)
  2. 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:

  1. Use in function calls
procedure Foo(i: Integer; s: String);
...

var
  p: specialize TPair<Integer, String>;
begin
  Foo(...p);
end;
  1. Allowing inside casts for record construction:
  TTestRec(42, 'Hello World', 3.14);
  1. 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;
  1. 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;
  1. 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.

Merge request reports

Loading