Description
Case
When we get Records, a table implementation could look like this:
class InfoTable3<C1, C2, C3, D> extends StatelessWidget {
InfoTable3({required this.columns, required this.rows});
final Record<InfoColumn<C1>, InfoColumn<C2>, InfoColumn<C3>> columns;
final Iterable<Record<InfoCell<C1>, InfoCell<C2>, InfoCell<C3>, data: D>> rows;
...
}
and be used like this:
InfoTable3( // InfoTable3<Datetime, double, SomeEnum, Data>
columns: (
InfoColumn("Date", toWidget: (date) => Text(date.someFormat())), // date here is inferred to be DateTime
InfoColumn("Amount", toWidget: (amount) => CoolWidget(amount)), // InfoColumn<double> is also inferred
InfoColumn("Type", sort: (a, b) => someSort(a, b), // both a and b will be SomeEnum
),
rows: listOfData.map(
(data) => (
InfoCell(data.time), // InfoCell<Datetime> since data.time is Datetime
InfoCell(data.amount), // InfoCell<double> since data.amount is double
InfoCell(data.type), // InfoCell<SomeEnum> since data.typeis SomeEnum
data: data,
),
),
);
}
This gives nice typing since we know that the compiler will make sure that our types of each cell in a row and each column match. For more info on this example see #2531.
Problem
There is a need for several InfoTableN implementations, which is a lot of repetitive code, and of course the user have to specify InfoTableN instead of just InfoTable.
Other possible solution
Metaprogramming
Metaprogramming might allow us to just write how each InfoTableN should be implemented and it would then generate a given number of implementations.
We would still have to specify the N in InfoTableN when using the table (just a minor inconvenience) but the metaprogramming might be a lot more difficult to implement or it might not support this use case. And there would still have to be generated all implementations of InfoTableN instead of just 1.
Main idea
Variadic Generics
We could specify using a class InfoTable (using normal dart + Variadic Generics) and having the number of argument in the Records be variable.
Here we would not have to generate multiple classes so we could create the table just by using InfoTable (no need to specify the N), and would not need metaprogramming which might be more complicated than the usual Dart.
In Python
Variadic Generics will be added to python 3.11 and the specs are in this PEP: https://peps.python.org/pep-0646/
Here is an example, (*iterable in python is similar to the ...iterable in Dart):
from typing import TypeVar, TypeVarTuple
D = TypeVar('D')
Cs = TypeVarTuple('Cs')
class InfoTable(Generic[D, *Cs]):
def __init__(self, *colums: *Cs, data: D | None =None):
pass
Example syntax in Dart:
class InfoTable<...Cs, D> extends StatelessWidget { // ...CS, a variable number of types, D is just 1 type as normal
InfoTable({required this.columns, required this.rows});
final Record<...InfoColumn<Cs>> columns; //
final Iterable<Record<...InfoCell<Cs>, data: D>> rows;
...
}
The idea behind the syntax is to use the same operator as in Python * however in dart the syntax is ... so we use that, and it must be used when specifying the generics as the example above: InfoTable<...Cs, D> (as in Python).
And then every time we use Cs (the name of our varadics) then it is understood as just one of the variable number of elements. and then we can put in in other types like InfoColumn and it must have a ... somewhere in front and only then will it fold out the types in a Record or other type that support a variable number of inputs (InfoTable could be used in some other code where some variadic generic ...As could be used for InfoTable<...As, D>)
Other example
Record<...Ts> waitRecord<...Ts>(Record<...Future<Ts>> tuple) async {
// Await a Record(Future<T1>, Future<T2>, ...., Future<TN>) and return Record<T1, T2, ....., TN>
}