[fpc-devel] RFC: Support for new type "tuple" v0.1

Sven Barth pascaldragon at googlemail.com
Sat Jan 26 18:43:54 CET 2013


Hello together!

Based on the results of the "for-in-index" thread I've decided to come 
up with a draft for the Tuple type which is thought by many people to be 
a better alternative to "for-in-index".

Please note the following points:
* This is not the final specification for Tuples and thus open to 
discussion (there are still some issues with this draft that need to be 
solved)
* I won't implement this feature myself (at least not in the near 
future) as I have other topics on my list (most importantly generics 
once type helpers are commited), so Alexander is free to give the task 
for implementation to his student.

Regards,
Sven

The draft:

* Description

What are tuples? Tuples are an accumulation of values of different or 
same type where the order matters. Sounds familiar? They are in this 
regard similar to records, but it's only the order of an element that 
matters, not its name. So what does make them special? Unlike records 
you can only query or set all the elements of a tuple at once. They 
basically behave like multiple assignments. In effect they allow you to 
return e.g. a multivalued result value without resorting to the naming 
of record fields (you'll still need to declare a tuple type) or the need 
for out parameters. This in turn allows you to use them for example in 
"for-in" loops.

* Declaration:

The declaration of a tuple type is build up as follows (pseudo grammar):

TUPLETYPE ::= [packed] tuple of (TUPLEELEMENTS)
TUPLEELEMENTS ::= TYPENAME, TYPENAME [, TYPENAME]*

This shows that a tuple must at least consist of two elements. While in 
theory a one element tuple would be possible the question is on the one 
hand why you'd use a tuple type at all and on the other hand this avoids 
potential problems for the compiler especially if the tuple type is used 
as the left side of an assignment (the compiler currently allows e.g. 
"(Writeln('Foobar'))" as a statement).
Note: If single element tuples are desired they need to be made 
assignment compatible with corresponding scalar values.

The memory layout of tuples is also similar to records in that the 
elements are aligned according to the current alignment settings. This 
alignment is not used if the "packed" modifier is used.

Like sets tuples can be declared anonymously:

=== code begin ===

var
   t: tuple of (Integer, String);

=== code end ===

Tuples can not be declared as generic, but they can be declared inside 
generics with generic parameters as one or more element types.

* Assignment compatibility:

Two different tuples A and B are considered assignment compatible if and 
only if they contain the same number of elements and each element Ai is 
assignment compatible to Bi (please note that this might contain 
precision/data loss).

E.g.:

=== code begin ===

var
   t1, t2: tuple of (Integer, Single, TObject, String);
   t3: tuple of (Byte, Double, TStrings, ShortString);
   t4: tuple of (Integer, Single);
   t5: tuple of (Single, Double, TObject, String);
   t6: tuple of (TObject, Integer, String, Single);
begin
   t1 := t2; // ok
   t1 := t3; // ok (with precision loss in second element)
   t3 := t1; // not ok, because TObject is not a subclass of TStrings
   t1 := t4; // not ok, because the count of elements is not equal
   t5 := t1; // ok, element 1 will be converted to a Single
   t6 := t1; // not ok, order of elements is not equal
end;
=== code end ===

* Usage:

Tuples can be used by assigning either another compatible tuple (see 
above) to it or by constructing or deconstructing a tuple value.

A constructor looks as follows:

TUPLECONSTRUCTOR ::= (RVALUE, RVALUE [,RVALUE]*)

While a deconstructor looks as follows:

TUPLEDECONSTRUCTOR ::= (LVALUE, LVALUE, [,LVALUE]*)

In both cases the amount of elements must be equal to the amount of 
elements the tuple is assigned to/from (and also the types must be 
compatible).

Examples:

=== code begin ===
var
   t1: tuple of (Integer, String, Single);
   t2: tuple of (TObject, Integer);
   s: String;
   i: LongInt;
   d: Double;
   o: TObject;
   sl: TStringList;
begin
   // constructors
   t1 := (42, 'Hello World', 3.14);
   t1 := (i, s, d); // with precision loss in third element upon assignment
   t2 := (TObject.Create, 42);
   t2 := (Nil, 0);
   t2 := (TStringList.Create, Random(42));
   //t2 := (0, False); // not ok, because elements are not compatible
   //t2 := (Nil, 0, ''); // not ok, because the count is different
   // deconstrucors
   (i, s, d) := t1;
   //(42, s, d) := t1; // not valid, because 42 can not be assigned to!
   //(i, s) := t1; // not valid, because the count of elements differs
   (o, i) := t2;
   (o, sl.Capacity) := t2; // properties can be assigned to as well
end;

=== code end ===

The usage of constructors and destructors also allows a realisation of 
group assignment:

=== code begin ===

var
   a, b, e: Integer;
   c, d: String;
begin
   a := 42;
   c := 'Hello World';
   (b, d) := (a, c);
   a := 21;
   b := 84;
   (a, b) := (b, a); // the compiler needs to ensure the correct usage 
of temps here!
   a := 42;
   (a, e) := (a * 2, a); // (a, e) should be (84, 42), not (84, 84)
end;

=== code end ===

Tuples can be written the same way to a file ("file of ...") as a record 
can (the same restrictions/problems apply).

Also allowed is the usage of the following compiler intrinsics:

(Bit)SizeOf
TypeInfo

Type helpers may be defined for named tuple types.

* Operators

Only two operators are defined on tuples: Equality and Inequality. 
Whereby two tuples are considered equal if all their elements are 
considered equal. On the other hand two tuples are considered unequal if 
at least one of their elements is unqual.

E.g.

=== code begin ===

var
   t1, t2: tuple of (String, Integer, Float);
begin
   t1 := ('Hello World', 42, 3.14);
   t2 := ('Hello World', 42, 3.14);
   Writeln(t1 = t2); // true
   Writeln(t1 <> t2); // false
   t2 := ('Hello World', 41, 3.14);
   Writeln(t1 = t2); // false
   Writeln(t1 <> t2); // true
end;

=== code end ===

Other operators can be overloaded for a named tuple type. The only 
exception is for the assigment operators: they can only be overloaded if 
one side is not a tuple.

* Possible extensions

Note: This section is not completely thought through!

An possible extension would be to allow the assignment of tuples to 
records and/or arrays (and vice versa). For records the restriction must 
be that it must not be a variant record (aka "record with case"), 
because of possible type problems:

=== code begin ===

type
   TMyRecord = record
     case Boolean of
       True: (f1: Byte; f2: Word);
       False: (f3: Word; f4: Byte);
   end;

var
   t: tuple of (Byte, Word);
   r: TMyRecord;
begin
   t := (42, 42);
   r := t; // according to assignment compatibility both cases can be 
assigned to, but will result in different content in memory!
end;

=== code end ===

Also for arrays the type of the elements need to be equal (exception can 
be "array of Variant") and the count of elements needs to be the same 
(for static arrays at compile time and for dynamic arrays at run time 
(might need a new runtime error/exception type)). Dynamic arrays won't 
be dynamically resized.

* Possible uses

- use for group assignments which can make the code more readable
- use for multivalues return values which can make the code more 
readable (instead of using records or out parameters)
- use as result value for iterators (this way e.g. key and data of 
containers can be queried)

* Implementation notes

Tuples need to pay attention to managed types (strings, interfaces, 
etc.). Thus an Init RTTI will be required (which needs to be handled by 
fpc_initalize/fpc_finalize accordingly).
It might be worthwhile to add a new node type for tuple 
constructors/deconstructors (one node type should be sufficient) and 
handle them in assignment nodes accordingly.

* Open issues

Should anonymous tuples (together with tuple constructors) be allowed to 
participate in operator search as well? This would on the one hand allow 
the following code, but on the other hand make operator lookup rules 
less clear (because of assignment compatibility rules):

=== code begin ===

type
   TDoubleVector = tuple of (Double, Double, Double, Double);

operator + (aLeft, aRight: TDoubleVector): TDoubleVector;
// implement by e.g. using SSE instructions

// somewhere else
begin
   (d1, d2, d3, d4) := (d1, d2, d3, d4) + (1.0, 2.0, 3.0, 4.0);
end;

=== code end ===



More information about the fpc-devel mailing list