8

According to this article, it's possible, in Dart, to define a non-abstract class to have an abstract (or not-implemented) method. The abstract method causes a warning, but does not prevent instantiation.

What's the purpose of allowing the declaration of an abstract method in a non-abstract (or concrete) class in Dart? Why was Dart designed to work in this way?

nbro
  • 15,395
  • 32
  • 113
  • 196

2 Answers2

2

The specification is actually very explicit about declaring abstract methods in a concrete class:

It is a static warning if an abstract member m is declared or inherited in a concrete class


We wish to warn if one declares a concrete class with abstract members.


It is a static warning if a concrete class has an abstract member (declared or inherited).

They don't have any intended purpose for it, which is why they issue warnings. If you're familiar with Java: it's similar to accessing a static member via an object, which is also pointless and triggers a warning.

As for why it passes compilation, Dart uses an optional type system, which means typing concepts should not affect the semantics of the language, and that's simply what Dart is enforcing:

The purpose of an abstract method is to provide a declaration for purposes such as type checking and reflection.


The static checker will report some violations of the type rules, but such violations do not abort compilation or preclude execution.

Vince
  • 14,470
  • 7
  • 39
  • 84
0

An abstract method in a concrete class allows you to provide the type signature for a method that is implemented via noSuchMethod() instead. Providing a noSuchMethod() implementation will also silence the warning.

In strong mode, simply having an abstract method in a concrete class will result in an error, unless the class also implements the noSuchMethod() interface.

In short, the purpose of abstract methods in a concrete class is to provide type signatures for noSuchMethod() implementations. This avoids warnings for calling an unknown method and in strong mode (which is the default for dartdevc, and will be first the default and then mandatory for Dart 2.0) these type signatures are necessary for code with noSuchMethod() to even compile, unless the target is of type dynamic.

Example:

class A {
  void f();
  dynamic noSuchMethod(Invocation inv) => null;
}

void main() {
  var a = new A();
  a.f();
}

If we replace a.f() with (say) a.f(0), then this will result in an error (in strong mode) for having called the method with the wrong number of parameters. If we omit the void f() declaration, then we'll get an error that A does not have a method f(). If we omit the noSuchMethod() implementation, then the complaint will be that f() lacks a method body, even though A isn't abstract.

The following code provides a more realistic example:

import "dart:mirrors";

class DebugList<T> implements List<T> {
  List<T> _delegate;
  InstanceMirror _mirror;
  DebugList(this._delegate) {
    _mirror = reflect(_delegate);
  }
  dynamic noSuchMethod(Invocation inv) {
    print("entering ${inv.memberName}");
    var result = _mirror.delegate(inv);
    print("leaving  ${inv.memberName}");
    return result;
  }
}

void main() {
  List<int> list = new DebugList<int>([1, 2, 3]);
  int len = list.length;
  for (int i = 0; i < len; i++) print(list[i]);
}

This example creates a debugging decorator for List<T>, showing all method invocations. We use implements List<T> to pull in the entire list interface, inheriting dozens of abstract methods. This would normally result in warnings (or in strong mode, errors) when run through dartanalyzer, as we're missing implementations for all these methods normally provided by List<T>. Providing a noSuchMethod() implementation silences these warnings/errors.

While we could also manually wrap all 50+ methods, this would be a lot of typing. The above approach also will continue to work if new methods are added to the list interface without us having to change our code.

Use cases for explicitly listing methods in a concrete class are less common, but can also occur. An example would be the addition of getters or setters to such a debugging decorator that allows us to inspect or set instance variables of the delegate. We will need to add them to the interface, anyway, to avoid warnings and errors from using them; the noSuchMethod() implementation can then implement them using getField() and setField(). Here's a variant of the previous example, using stacks instead of lists:

// main.dart

import "dart:mirrors";
import "stack.dart";

class DebugStack<T> implements Stack<T> {
  Stack<T> _delegate;
  InstanceMirror _mirror;
  DebugStack(this._delegate) {
    _mirror = reflect(_delegate);
  }

  dynamic _get(Symbol sym) {
    // some magic so that we can retrieve private fields
    var name = MirrorSystem.getName(sym);
    var sym2 = MirrorSystem.getSymbol(name, _mirror.type.owner);
    return _mirror.getField(sym2).reflectee;
  }

  List<T> get _data;

  dynamic noSuchMethod(Invocation inv) {
    dynamic result;
    print("entering ${inv.memberName}");
    if (inv.isGetter)
      result = _get(inv.memberName);
    else
      result = _mirror.delegate(inv);
    print("leaving  ${inv.memberName}");
    return result;
  }
}

void main() {
  var stack = new DebugStack<int>(new Stack<int>.from([1, 2, 3]));
  print(stack._data);
  while (!stack.isEmpty) {
    print(stack.pop());
  }
}

// stack.dart

class Stack<T> {
  List<T> _data = [];
  Stack.empty();
  Stack.from(Iterable<T> src) {
    _data.addAll(src);
  }
  void push(T item) => _data.add(item);
  T pop() => _data.removeLast();
  bool get isEmpty => _data.length == 0;
}

Note that the abstract declaration of the _data getter is crucial for type checking. If we were to remove it, we'd get a warning even without strong mode, and in strong mode (say, with dartdevc or dartanalyzer --strong), it will fail:

$ dartdevc -o main.js main.dart
[error] The getter '_data' isn't defined for the class 'DebugStack<int>' (main.dart, line 36, col 15)

Please fix all errors before compiling (warnings are okay).
Reimer Behrends
  • 8,600
  • 15
  • 19
  • That sounds like misuse/abuse of `noSuchMethod` - why would you put the implementation of `f` in `noSuchMethod` rather than simply giving `f` the implementation itself? – Vince Aug 17 '17 at 20:22
  • As a simple example, consider the case where the abstract method is not listed explicitly, but comes from an `implements` or `extends` clause and `noSuchMethod()` delegates to the actual implementation of the inherited interface. – Reimer Behrends Aug 17 '17 at 20:44
  • 1
    You're saying if the supertype forces the subtype to irmplement the behaviors, `noSuchMethod` could delegate to the supertype's implementation. That doesn't sound off to you? Even if `SuperA` forced `Sub` to implement methods, and `SuperB` held the implementation, there would be no need for `noSuchMethod`. From the spec: "*We wish to warn if one declares a concrete class with abstract members. However, code like the following should work without warnings*" followed by a code example expressing that. The question is questioning *explicitly* declaring abstract methods on concrete classes. – Vince Aug 17 '17 at 20:53
  • How familiar are you with automated delegation and its use cases? Because that's one of the most traditional use cases for `#doesNotUnderstand:` or `noSuchMethod()` or similar language features. Delegation is being used when implementing the methods directly or inheriting a mixin isn't a solution. Practical examples include implementations that rely on reflection, implementations that are switchable at runtime, the Decorator pattern, or where the implementation is in an object created by a third-party library. – Reimer Behrends Aug 17 '17 at 21:19
  • Very familiar. Those methods exist for delegating when there is no known/declared method. Dart [specifies](https://www.dartlang.org/articles/language/emulating-functions#interactions-with-mirrors-and-nosuchmethod) it's delegation use case: "*In Dart, you can customize how objects react to **methods that are not explicitly defined in their class chain** by overriding noSuchMethod()*". His situation involves explicitly declaring the method, I don't see why you felt I was unfamiliar. – Vince Aug 17 '17 at 21:45
  • Because you seemed to assume that all use cases of delegation could be covered by using `SuperB`'s implementation in some other way and didn't even consider the case that there might not be a singular `SuperB` or a way to construct it at compile time. But delegation is mostly useful when dispatch logic has to be deferred until runtime or as a simple meta-programming technique (or a combination thereof). – Reimer Behrends Aug 17 '17 at 21:57
  • As for declaring explicit abstract methods on concrete classes, it would only complicate the language if there was a distinction between inherited abstract methods and explicit abstract methods in a concrete class. – Reimer Behrends Aug 17 '17 at 21:58
  • I only said that because of "*comes from an `implements` or `extends` clause and `noSuchMethod()` delegates to the actual implementation **of the inherited interface***". Using `noSuchMethod` in this way (when the function is declared) is pretty much pointless - why not simply give the delegation responsibility to `f` rather than `noSuchMethod`? Scaling this up slightly, you'd see not only the excess verbosity, but the monolithic nature you'd be giving `noSuchMethod`. ***EDIT:*** There *is* a distinction in the spec, see the first quoted statement of my answer – Vince Aug 17 '17 at 22:08
  • I have added additional example code and explanations to address your concerns. As for the spec, your quotes do not seem to make distinctions between declared and inherited abstract members. – Reimer Behrends Aug 17 '17 at 23:57
  • 1
    Your example doesn't declare an abstract member; it doesn't reflect the question, it only shows an example of `noSuchMethod`, and adding an abstract member wouldn't change anything. If you added an abstract member and implemented it via `noSuchMethod`, there would be no reason for the abstract method *not* to manage the implementation. As for the distinctions: "*It is a static warning if a concrete class has an abstract member **(declared or inherited)***" - "**It is a static warning if an abstract member m is **declared or inherited** in a concrete class*" – Vince Aug 18 '17 at 00:20
  • (1) The example code inherits the members of a class as abstract members. This is what `implements` (as opposed to `extends`) does. (2) I also sketched out an example (without code) for declaring abstract members within the concrete class. (3) The first paragraph of the original question is about both cases, not just one; I thought it prudent to be exhaustive. (4) Your quotes both say "declared or inherited", not only "declared" or only "inherited", so I'm not sure what you're trying to say; Dart treats both declared and inherited abstract methods the same way here. – Reimer Behrends Aug 18 '17 at 00:42
  • What I'm trying to say is the question is about declaring an abstract method in a concrete class. Your first example shows NO purpose for `f`, declaring `f` gives no value since you could do the same without declaring `f` for the same results. You then explain how `noSuchMethod` can be used, even though it applies if an method (abstract or not) *isn't* declare in the concrete class (or base class) - it's like the actual question was dodged. He wasn't asking about `noSuchMethod`, or inheriting abstract members.. – Vince Aug 18 '17 at 02:07
  • I think I may understand where the confusion comes from. I'm using `noSuchMethod()`, because *that's what the spec tells us* makes for a valid use of abstract methods in concrete classes. My first example is a minimal, pared-down example to illustrate the mechanism. At that point I thought that use cases would be obvious (such as *automated* delegation; the automated part, i.e. where you don't supply the method body, e.g. for DRY reasons, is important). My second example gives a practical application for *inherited* methods; the third example then extends it to *declared* methods. – Reimer Behrends Aug 18 '17 at 08:21
  • 1
    Re: "You then explain how noSuchMethod can be used, even though it applies if an method (abstract or not) isn't declare in the concrete class (or base class)". If you don't declare the method, you get a warning (or in strong mode, an error) if you try to call it. This is what makes the declaration of the abstract method even in the presence of `noSuchMethod()` necessary. – Reimer Behrends Aug 18 '17 at 08:26