[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