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

Alexander Klenin klenin at gmail.com
Sat Jan 26 21:45:28 CET 2013


On Sun, Jan 27, 2013 at 4:43 AM, Sven Barth <pascaldragon at googlemail.com> wrote:
> 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.
>

Heh, I have started to write similar in form, but different in
substance proposal, but needed some sleep,
and you have beaten me to it :)

I want to quickly summarize the most important points of my proposal
before writing it out in length:

1) In a form proposed by Sven (and, IIUC, implemented in Oxygen),
tuples are not different enough from records
(so perhaps a term like "anonymous records" is preferable, as well of
re-using keyword "record").
I agree that anonymous records might be good, but I consider them a
separate, and weaker, extension.

2) The most important differentiating features of my proposal are:

2.1) Tuples are always temporary and anonymous. You can not store a
tuple, define tuple type, of variable of tuple type.
  So tuples are 100% static, compile-time feature -- no change to
RTTI, variants etc.

2.2) Tuples construction: after some thinking, I propose to define a
plain comma as an operator creating a tuple.
  This way, any comma-separated list is considered a tuple, and
parenthesis may be used simply to control priority,
  since priority of comma is lower than all other operators. I will
demonstrate below how the compatibility with existing features is
still preserved.

2.2.1) Tuples are *flattening*, so (1, 2, (3, 4)) is a tuple of 4
elements, not of 3 elements, last being tuple. Single-value tuples are
useless,
  so Tuple(1) is either a no-op or error.

2.2.2) Any record or array type may be converted to a tuple of its
elements using either assignment or "Tuple" intrinsic.
For example: Tuple(ARectangle) is a tuple of 4 elements;
x, y := APoint; // same as x := APoint.x; y := APoint.y;

2.3) Tuples deconstruction: this is quite similar to what Sven
described, with some additions:
2.3.1) Each tuple element may, or may not, be an lvalue (i.e. the
expression which may be assigned to).
  When tuple appears on the left side of assignment, all elements must
(obviously) be lvalues.
2.3.2) When tuple appears inside of a function/procedure argument, it
is deconstructed, and its elements are passed as a separate arguments.
  Tuple elements corresponding to var, out and constref parameters
must be lvalues. Note that due to (2.2.2) tuple may represent only
part of arguments.
  Together with (2.2) we note that normal semantics of procedure
parameters is left unchanged :)
  We also gain this:

procedure SomeLibrary.DrawLine(X1, Y1, X2, Y2, Color: Integer); //
Author did forgot/did not want to declare overload with "TRect"
parameter.
...
DrawLine(Tuple(ClientRect), clRed); // No need, tuples help here

2.3.3) When tuple is encountered inside square brackets, it behaves as
if its elements were listed instead:
s := Format('%d-%d', [ Tuple(CenterPoint(ARect)) ]);
Note that no changes to Format is required, it receives the usual
array of [X, Y].

2.3.4) Tuples may be either assigned or casted to records, thus
gaining record constructors:
var
  r: TRect;
...
r := (1, 2, 3, 4); // Parenthesis not required.
r := TRect(1, 2, 3, 4); // Equivalent

2.3.5) Tuples may be either assigned or casted to arrays, thus gaining
array constructors:
var
  coordsArray: array [1..4] of Integer;
  arr: array of Integer;
...
coordsArray := 1, 2, 3, 4;
coordsArray := Tuple(ARect);
coordsArray := Tuple(APoint1), Tuple(APoint2); // flattening

// Harder to implement, but much nicer than usual
SetLength(arr, Length(arr) + 1);
arr[High(arr)] := newItem;
// with tuples:
arr := Tuple(arr), newItem;

It might be of some value to require [] around tuple just for
consistency with existing open array syntax.

2.3.6) Array indexing operator [] already may be overloaded with a key
of record type.
  If the tuple is used as an array index, such overload will be
considered before the usual successive application
  of each tuple element:
type
  TSparseMatrix=class
  end;
operator[](A: TSparseMatrix; AIndex: TPoint): Double;
...
m[3, 5] // calls the above opeartor instead of m[3][5].

2.3.7) nil is considered acceplable as an lvalue element of a tuple.
Anything value assigned nil element is simply dropped:
var
  a: TMyContainter; // Container author declared only CurrentKey
property, but not old-style Current property for the enumerator
...
for v in a do // type error, can not assign tuple to a single value
for v, nil in a do // works
for nil, k in a do // loop over keys only, may be useful for
containers like STL sets

2.4) Since tuples can now be used to deconstruct return value of a
function, the restriction forbidding
  anonymous return types may be lifted (see paragraph (1)):
function AcquireResource(AName: String): record AResource: THandler;
AError: String; end;
  Of course, such functions may only be used by treating their result
as a tuple.
  Further, it may make sense to introduce shortcut proposed by Seven,
allowing to write return type as just
  (THandler, String). However, this is rather minor additional step,
so I am not sure is worth it.
  The important part -- clarifying the call site -- will happen anyway.

2.5) For starters, I would not define any operators on tuples, thus
requiring to cast them to records.
Variant of Sven's example:

type
  TDoubleVector = array [1..4] of Double;
operator + (aLeft, aRight: TDoubleVector): TDoubleVector; // implement
by e.g. using SSE instructions
...
  d1, d2, d3, d4 := TDoubleVector(d1, d2, d3, d4) + TDoubleVector(1.0,
2.0, 3.0, 4.0);

2.5.1) However, if comparison operator is defined on tuples, then it
might be useful to also define lexicographical ordering
(i.e. A < B if first non-equal element of A is less then in B).
For example (taking lambda syntax from anonymous method thread):

generic Sorter<TElem> = class
  type TCmp = reference to procedure (const A, B: TElem);
  class procedure Sort(A: array of T; ACmp: TCmp);
end;
PointSorter = specialize Sorter<TPoint>;

// sort points top-to-bottom, then left-to-right:
PointSorter.Sort(a, lambda PointSorter.TCmp as Tuple(A) < Tuple(B));

--
Alexander S. Klenin



More information about the fpc-devel mailing list