Skip to content

Consider adding ".classType" or "Type.classOf" #31459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
matanlurey opened this issue Nov 27, 2017 · 10 comments
Open

Consider adding ".classType" or "Type.classOf" #31459

matanlurey opened this issue Nov 27, 2017 · 10 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug

Comments

@matanlurey
Copy link
Contributor

Today runtimeType is (mis?)used for the following scenarios:

See usage in Flutter for some background.

Also see issues with .runtimeType in Dart2JS: #31329

Ease of use in implementing operator==

class A {
  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != this.runtimeType) {
      return false;
    }
    final A typedOther = other;
    // .. do checks ..
  }
}

Examples

Checking that the type of an object is exactly something:

abstract class A {}
class _ConcreteA implements A {}

void useA(A a) {
  if (a.runtimeType != _ConcreteA) {
    throw 'Do not subclass "A"';
  }
}

Examples:

Note that is check would not be necessary with #27372.

Outputting debug information about a class instance:

abstract class A {
  var field1;
  var field2;

  @override
  String toString() => '$runtimeType: {$field1, $field2}';
}

Examples


I believe most of these cases could be addressed without more expensive reified types by introducing a .classType or Type.classOf. In JavaScript this could simply map to the already present prototype:

class Type {
  // Pardon my weak dart2js-foo.
  @patch static Type classOf(dynamic instance) => wrapJsType(JS('#.constructor', instance));
}

One of the cases it doesn't address is generic types, for example:

class A<T> {}

void main() {
  var a = new A<String>();
  print(Type.classOf(a)); // 'A' not 'A<String>'
}

... but that might be reasonable, and allow better optimizations in most scenarios.

@matanlurey
Copy link
Contributor Author

/cc @rakudrama @sigmundch to sanity check my idea.

@rakudrama
Copy link
Member

It might be better to have a separate feature for each use-case.
I'd rather not encode these ideas as properties of an instance of Type.

if (Type.sameClass(this, other)) ...
if (a is exactly _ConcreteA) ...
print('${Type.classNameOf(this)} ...');

The dart2js type representation is going to change a lot in the future and constructors will not map simply to types.

@matanlurey
Copy link
Contributor Author

Sure, those suggestions above make sense to me.

@zoechi
Copy link
Contributor

zoechi commented Nov 28, 2017

Seems there are plans already

The Object.runtimeType is being removed and replaced by Type.of(object). The idea of being able to emulate other classes by changing the runtimeType sounds interesting, but since Dart doesn't support any means to also manipulate the is operator, its uses are extremely limiting. The static Type.of function is less error-prone and more efficient for compilers.

https://github.com/dart-lang/sdk/blob/master/docs/newsletter/lib/lib.md

@lrhn
Copy link
Member

lrhn commented Nov 28, 2017

I'm not sure how Type.classOf will be more efficient than Object.runtimeType, apart from now being virtual. In the provided examples, you would still create a Type object.
The planned Type.of will act exactly like runtimeType, so the type objects are the same.

That is, I don't see what feature is being requested, or if it's just Type.of, how it will actually be more efficient than runtimeType.

In most cases, I would recommend just using is checks.

 bool operator ==(dynamic other) {
    if (other is A) {  // even promotes for you. Allows mocks!
      // .. do checks ..
    }
    return false;
  }

  void useA(A a) {
    if (a.runtimeType is! _ConcreteA) {  // If you don't subclass the private class yourself, nobody will.
      throw 'Do not subclass "A"';
    }
  }

The toString is useful, but we never promised what the toString of Type gives, so it's also relying on unspecified behavior.

Generics is a problem when you try to use runtime types for type comparisons (and the current solution is to just not do so). A way to get a non-generic representation of a generic class type is also interesting.

@eernstg
Copy link
Member

eernstg commented Nov 28, 2017

Acknowledging the points that Stephen and others made already, we have had requests for a feature like Type.classOf(object) which would evaluate to a Type representing the class of that instance without any type arguments (this is sometimes designated as the originalDeclaration, but I'd prefer a terminology like "a class A<T> which is a generic instantiation of the generic class A", and then we're talking about support for obtaining a Type which represents the generic class of the class of the given instance).

That could be a new kind of instance of type Type, or it could be a canonical representative (for instance, having the type arguments obtained by instantiate-to-bound; with that, List-the-generic-class would be represented by the Type for List<dynamic>). A non-trivial trade off, as usual.

In particular, it would be helpful for the package reflectable to be able to get the generic class of the class of a given instance, in order to create an appropriate instance mirror for it. Moreover, it was perceived as a clear improvement to be able to get the generic class rather than the plain runtimeType, because the latter would serve as evidence that the type arguments must be represented at run time whereas the former would allow those type arguments to be erased at compile-time, in situations where no other parts of the program would require these type arguments to be present at run time.

So it might actually be helpful for the space (and maybe time) economy to have this type of feature.

Returning to Matan's remark:

One of the cases it doesn't address is generic types ..

I'd prefer to say that the ability to get the runtimeType and the ability to get the generic class of a given instance are different features, with different trade-offs in terms of resource usage. It makes sense to be able to do both, and it's simply incorrect to expect them to be interchangeable --- they are doing different things, and the source code using them should be written accordingly.

One tricky issue is what to do when the generic class of an instance of a non-generic class is requested (say, a String: no type arguments in sight, but we might return the generic class representation of Comparable because it is a subtype of Comparable<String>). But that kind of issue is just a technicality: It needs to be sorted out before we can have such a feature, but there's no reason to assume that it is impossible to come up with a reasonable approach.

@matanlurey
Copy link
Contributor Author

@lrhn:

I'm not sure how Type.classOf will be more efficient than Object.runtimeType.

Simple. In JavaScript, every class has a function type: instance.constructor. There is no need to store extra RTTI to implement this feature, it can be done fairly efficiently out of the box. For example:

class Foo {}

function example() {
  var foo = new Foo();
  if (foo.constructor == Foo) {
    // ...
  }
}

To "match" Dart semantics, we could use close to @rakudrama's suggestion:

class Foo {}

void main() {
  var foo = new Foo();
  if (Type.isExactly(foo, Foo)) {
    // ...
  }
}

No overhead.

In most cases, I would recommend just using is checks.

Well, not everyone is (pun intended). See all of the Flutter examples above. There is (unfortunately) a lot of examples of this internally for web clients too. It seems like .runtimeType is just not a feature we can support well on the web: #31329.

@lrhn
Copy link
Member

lrhn commented Nov 28, 2017

So you are saying that Type.classOf does not return a Type for the object's runtime type, but one representing its class (which is different for generic classes). It could return a Class object, for that matter (which is not a Type).

The examples used non-generic classes, and you seemed concerned that it doesn't handle the generic case, so it seemed like you wanted something that did reflect the runtime type, not the class of the runtime type. In that case I couldn't see any difference between that and Type.of, and no performance difference between that and runtimeType.

@karlklose
Copy link
Contributor

karlklose commented Nov 29, 2017 via email

@lrhn
Copy link
Member

lrhn commented Nov 29, 2017

I absolutely believe that a "class reflection" is more efficient than a "type reflection" (and often more useful), I just wasn't sure what was actually asked for here.

@dgrove dgrove added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core labels Nov 29, 2017
@lrhn lrhn added the type-enhancement A request for a change that isn't a bug label Sep 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

7 participants