Description
I have been trying out the new records feature, and while they are a definite upgrade to the language, they seem somewhat non intuitive to use for someone coming from programming mostly in F# and Typescript.
Consider the following code,
typedef Quote = ({
DateTime date,
Decimal open,
Decimal high,
Decimal low,
Decimal close,
Decimal volume
});
typedef SMAResult = ({DateTime date, double result});
abstract interface class TSeries {
DateTime get date;
}
typedef Series<T extends TSeries> = Stream<T>;
// This wont work, you'd have to create an entire
// class when a record would be simpler
// as there might be many different simple variations of the T constraint.
Series<Quote> createSeries(Iterable<Quote> quotes) async* {
for (var quote in quotes) {
yield quote;
}
}
Stream<SMAResult> simpleMovingAverage(Series<Quote> inputStream, int lookBack) async* {
List<double> window = [];
await for (Quote value in inputStream) {
window.add(value.close.toDouble());
if (window.length > lookBack) {
window.removeAt(0);
}
yield (date: value.date, result: window.reduce((a, b) => a + b) / window.length);
}
}
consider there is a library full functions that return different result types similar to the SMAResult, lets say I want to accept anything with the shape
({DateTime date, double value})
as opposed the the Quote type in the simpleMovingAverage function, which would allow calculation of results from other functions that do similar calculations.
// will not work
Stream<SMAResult> simpleMovingAverage(
Series<({DateTime date, double result})> inputStream, int lookBack)
In situations like this, where it's much simpler to use records as opposed to classes, mainly due to the structural equality aspects of the type being implemented for you, it would be nice for records to be able to implement interfaces.
Coming from F# this is kind of second nature
type ISeries =
abstract member Date: DateTime with get
type SMAResult =
{
_date: DateTime
Result: double
}
interface ISeries with
member x.Date = x._date
converted to dart I am guessing that would be something similar to
typedef SMAResult = ({@override DateTime date, double result}) implements TSeries;
in typescript this is all much easier because the entire type system is structural so every
type is an interface itself, which makes thing a lot simpler, no interfaces to define etc.
Currently, not being able to use records in these types of contexts seems somewhat limiting IMO, if I only need a bag of values and no methods, but I need to ensure every type has some subset of fields, it's not currently possible (that I am aware of ) in the current implementation of records.
Another option (that does't seem feasible) is so called "Statically Resolved Type Parameters" which are simply member constraints, and allow a form of duck typing.
// Require `TResultA to have a member Date of type DateTime
let inline syncIndex<'TResultA, 'TResultB when 'TResultA: (member Date: DateTime) and 'TResultA: equality>
(syncMe: 'TResultA alist)
(toMatch: 'TResultA alist)
Maybe there is a way to accomplish the same effect that I am unaware of? If not, I would consider this to be a nice feature to have and allow better overall use of records.