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 ===

_______________________________________________
fpc-devel maillist  -  [email protected]
http://lists.freepascal.org/mailman/listinfo/fpc-devel

Reply via email to