adimetrius писал(а):
Тогда, во-вторых, можно, например, сделать проверку соответствия фактических типов в присваивании, подобно проверке индексов. И при нарушении соответствия типа в fa[i] := u производить авост.
Придется ввести
* понятие "терминального типа массива": в объявлении
...
* и какое-то обозначение для него, чтобы производить проверку:
Для агрегатных типов можно выкрутиться без дженериков, магической рефлексии и "особых алиасов на типы". Для Паскаля когда-то был (собственно-то, и есть) неплохой концепт контейнеров-"всемогуторов":
http://sourceforge.net/projects/adot/Код:
{
General format of ADOT type construction:
<DataType>.Create(<ContainerType> [,Field1Type [,Field2Type ...]])
where
<DataType> is any data-type class (TTString, TTByte, TTPointer, TTInteger, ...)
<ContainerType> is any container class (TCVector, TCList, TCMap, TCHeap, ...)
Examples:
TTInteger.Create(TCSet) // set of integers
TTWideString.Create(TCMap, [TTInteger]); // map of string keys with integer values
TTDouble.Create(TCVector, [TTDouble, TTString]) // vector of doubles with two additional fields
TTString.Create(TCSet, [TTByte]) // set of strings with one additional field
...
You don't have to remember all data-type classes. ADOT supports ALL Delphi
built-in data types. Just write TT<type> where <type> is what you need
(TTByte, TTString, TTInteger, TTDouble, ...)
IMPORTANT: DO NOT CALL DESTRUCTOR FOR ADDITIONAL FIELDS. THEY WILL BE
DESTROYED AUTOMATICALLY FROM DESTRUCTOR OF MAIN FIELD.
}
...
// demonstrates creating of container with one field (TCVector, TCSList,
// TCDList, TCPriorityList, TCStack, TCDeque, ...)
procedure Demo1;
var
n : TTInteger;
m : TTDouble;
begin
writeln;
writeln('Vector of integers');
n := TTInteger.create(TCVector);
n.add([1,2,3,7,6,5]);
n.Println(ffDefault, true);
n.free;
// Some containers MUST have two fields at least: TCMap, TCMultimap,
// TCUnsortedMap. But we can add any number of additional fields to
// any container!
n := TTInteger.create(TCList, [TTDouble]);
m := n.fields[1] as TTDouble;
n.AddRecord([1, 0.999]);
n.AddRecord([2, 1.785]);
n.AddRecord([3, 3.1415926]);
m[n.Add(4)] := 4.12;
m[n.Add(5)] := 5.07;
writeln;
writeln('List with two fields');
n.println;
n.free;
end;
...
// demonstrates creating of container with two field (TCMap, TCMultimap,
// TCUnsortedmap)
// for mapping of doubles to strings
procedure Demo2;
var
n : TTDouble;
s : TTString;
begin
writeln;
writeln('Multimap(key: double; value: string)');
n := TTDouble.create(TCMultimap, [TTString]);
s := n.Fields[1] as TTString;
// set some values with .Map property
n.Map[3.14] := 'Pi';
n.Map[2.72] := 'E';
// another way to add data
// now we have full checking of types in compile-time
s[n.Add(1.618)] := 'Golden Ration';
s[n.Add(1.618)] := 'Golden';
// reassign
n.Map[3.14] := 'pi';
s[n.Find(2.72)] := 'e';
n.Println;
n.free;
end;
...
procedure Demo1;
var
n: TTInteger;
h: TCHandle;
i,j: integer;
begin
writeln;
writeln('Vector of integers');
n := TTInteger.create(TCVector);
n.add([1,2,3,7,6,5]);
// correct way to access items for any container
h := n.First;
for i:=0 to n.Count-1 do
begin
write(n[h], ' ');
n.Next(h);
end;
writeln;
// another way
h := n.First;
while h<>-1 do
begin
write(n[h], ' ');
n.Next(h);
end;
writeln;
// one more way
h := n.First;
while h<>-1 do
write(n[n.MoveNext(h)], ' ');
writeln;
// accessing items by index (correct only for TCVector)
for i := 0 to n.Count-1 do
write(n[i], ' ');
writeln;
// accessing items by index (correct for all containers)
for i := 0 to n.Count-1 do
write(n[n.handles[i]], ' ');
writeln;
// find sum
j := 0;
for i := 0 to n.Count-1 do
inc(j, n[n.handles[i]]);
writeln('sum: ', j);
n.free;
end;
В общем, используется популярный принцип "dataset" в тех краях и в те времена -- введение понятие "полей" агрегата как сущности "высшего порядка" (к слову, без возможности в языке определять операцию "индексация", вероятно, интерфейс будет не таким удобным, а так -- как с массивами). Такой приём избавляет от потребности использовать какие-то универсальные вариантные типы (ака Variant в том же Паскале) с соответствующим оверхедом или манипулировать указателями (с проверками и приведением типов). Внутренняя реализация контейнеров довольно универсальна, в основном базируется на "ручном" манипулировании байтами, и объекты-поля нужны для "предметной" интерпретации этих байтов (нет "инстанцирования шаблонов" и раздутия кода).
Недостатком является потребность в реализации полей-прокладок для своих типов (к тому же, есть некая "типо-некорректность", напр., свойство ".Map" (в коде выше) доступно лишь тогда, когда "поле" является "главным" или первичным и в контейнере вида Map, необходимы соответствующие assert-ы, которые однако можно отключать для производительности). А в принципе-то, в основном достаточно и базовых типов. Сейчас древний "dataset" после
Jai преподносится как новая парадигма
"Data-oriented design" (
Хабр1 ,
Хабр2,
Хабр3).