Skip to content

Allow records to implement interfaces #3071

Closed
@g5becks

Description

@g5becks

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureProposed language feature that solves one or more problemsstate-rejectedThis will not be worked on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions