Can Dart support abstract type member use cases?

50 Views Asked by At

Scala has a concept of abstract type members that, among other things, allow essentially defining legal mappings between types without having to repeatedly specify potentially invalid combinations of type parameters. For example, when modeling domain entities abstractly, where an entity has a specific id type, we can write something like

trait Entity {
    type Id
}

And then use this trait as

trait UserId
class User extends Entity {
    override type Id = UserId
}
trait ProductId
class Product extends Entity {
    override type Id = ProductId
}
// etc

This way, when we want to operate on Entitys generically, we can just refer to [X <: Entity], as opposed to also have to provide an Id (ie we don't have to specify something like [Id, X <: Entity[Id]]). This is both more ergonomic and ensures correctness when referring to an Entity and it's Id type.

Is it possible to support anything along these lines with Dart?

EDIT: I have an idea for a workable alternative, but not sure if it's possible at all. Dart allows you to access the type of a generic parameter as a value. Is it possible to get type parameters of type, that could allow something like

abstract class Entity<Id> { ... }

class Repo<E extends Entity> {
    final t = E; // here we can refer to the type E as a value

    // can you then use `t` to access type parameters it may have?
    // something like `t.typeParameters`... 
}

1

There are 1 best solutions below

0
lrn On

The only kind of type parameterization that Dart allows is generics.

That means doing something like:

abstract interface class Entity<Id> {}
class User implements Entity<UserId> {}
class Product implements Entity<ProductId> {}

That doesn't allow you to use the type for anything. The easiest way to get that is something like:

abstract base class Entity<Id> {
  R runWithId<R>(R Function<X>(Entity<X> self) action) => action<Id>(this);
}
final class User extends Entity<UserId> {}
final class Product extends Entity<ProductId> {}

Then you can do something like:

  Entity<Object?> entity = someEntity;
  entity.runWithId(<Id>(idEntity) {
    // Can use `Id` as  a type and `idEntity` of type `Entity<Id>`.
    List<Id> entities = [idEntity]; // fx.
  });

That gives you access to the actual type argument of the Entity instance, not just the static type. There is currently no way to access that from the outside, it requires cooperation from the class itself.