1

I am trying to implement a persistent Stack data structure. I want to implement this as an algebraic data type, so it has two concrete subtypes: empty and non empty:

abstract class Stack<T> {
  factory Stack.empty() => const _EmptyStack._();

  T get data;
  Stack<T> get bottom;
  bool get isEmpty;
  Stack<T> put(T item) => new _StackImpl(item, this);
}

class _StackImpl<T> extends Stack<T> {
  final T _data;
  final Stack<T> _bottom;

  _StackImpl(T this._data, Stack<T> this._bottom);

  T get data => _data;
  Stack<T> get bottom => _bottom;
  bool get isEmpty => false;
}

class _EmptyStack<T> extends Stack<T> {
  const _EmptyStack._();
  T get data => throw new CollectionIsEmpty();
  Stack<T> get bottom => throw new CollectionIsEmpty();
  bool get isEmpty => true;
}

This code raises two errors in concrete implementations:

[error] The class 'Stack' does not have a default generative constructor

I found a sample code which seem to address this problem here, so I've fixed it by putting a parameterless constructor in Stack<T> class:

abstract class Stack<T> {
    Stack();
    // ...

but now this causes problem with _EmptyStack<T> constructor, which is constant:

Constant constructor cannot call non-constant super constructor of 'Stack<T>'

Additionally the added Stack() constructor prevents from using the class as a mixin.

These restrictions seem to enforce on the class author to think about how the class would be extended. The way of extending List class from dart:collection package seem to confirm this conclusion - there is an entire separate class to use for extension, I can't directly extend the List class itself.

My question is more general then the problem described above: how can I write a class so that it can be flexible enough to extend? That includes allowing the use of features like:

  1. factory constructors in super class
  2. normal constructors in sub class
  3. const constructors in sub class
  4. be used as a mixin

While I understand that the use as mixin might be impossible or even unwanted, other points are still valid. Most importantly the question stands: why can't I extend a class with a factory constructor? This is a behavior unlike any other OO language I'm familiar with.

Also related questions:

EDIT: Thanks to Günter Zöchbauer answer I've improved the code, so now it is fully operational (see below). The most important question that I am now left with is: why factory constructor breaks the ability to extend the class? And how to get around it (aside from using the base class as interface)? A simpler example to make the point:

class Base {
}

class _Sub extends Base {
  int someValue;
  _Sub(int this.someValue);
}

Everything is fine with this code. But let's say I get back to my Base class in time and want to add factory method:

class Base {
    factory Base.empty() => new _Sub(0);
}

Now each class which extends Base is broken because of unresolved implicit call to super constructor. What do I do then?

Corrected code from original question for reference:

abstract class Stack<T> {
  const Stack._();
  factory Stack.empty() => const _EmptyStack._();

  T get data;
  Stack<T> get bottom;
  bool get isEmpty;
  Stack<T> put(T item) => new _StackImpl(item, this);
}

class _StackImpl<T> extends Stack<T> {
  final T _data;
  final Stack<T> _bottom;

  _StackImpl(T this._data, Stack<T> this._bottom) : super._();

  T get data => _data;
  Stack<T> get bottom => _bottom;
  bool get isEmpty => false;
}

class _EmptyStack<T> extends Stack<T> {
  const _EmptyStack._() : super._();
  T get data => throw new CollectionIsEmpty();
  Stack<T> get bottom => throw new CollectionIsEmpty();
  bool get isEmpty => true;
}

void main(){

  group('stack', (){

    test('empty stack', (){
      var emptyStack = new Stack.empty();
      expect(emptyStack.isEmpty, isTrue);
      expect(() => emptyStack.data, throwsA(new isInstanceOf<CollectionIsEmpty>()));
      expect(() => emptyStack.bottom, throwsA(new isInstanceOf<CollectionIsEmpty>()));

      var emptyStack2 = new Stack.empty();
      expect(emptyStack == emptyStack2, isTrue);
    });

    test('adding to stack', (){

      var stack = new Stack<String>.empty().put("a").put("b").put("c");

      expect(stack.data, equals('c'));
      expect(stack.bottom.data, equals('b'));
      expect(stack.bottom.bottom.data, equals('a'));

    });

  });

}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Maciej Sz
  • 11,151
  • 7
  • 40
  • 56
  • @GameAlchemist The design is a fundamental principle of functional programing and it does work. See [this answer](http://stackoverflow.com/a/10045677/1697320) for more insight on the subject. Additionally: as I have stated my question is more general then this particular case. It is only an example. – Maciej Sz Nov 02 '14 at 14:47

1 Answers1

1

In your example I suggest to just use Stack as an interface instead of a base class.

  1. factory constructors in super class If you have a factory constructor you have to add a normal constructor too if you want to extend as stated in the answer to the linked question.

  2. normal constructors in sub class What was the actual question here? I guess this is the same as 1.

  3. const constructors in sub class If you want a const constructor all subclasses need to have a const constructor too. In a class with a const constructor all fields need to be final. This isn't the case with your base class so where is the point of adding a const constructor to _EmptyStack.

  4. be used as a mixin The restrictions for classes to be used as a mixin are temporary and should be removed at some point.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • The interface solution is not good enough. Why would I have to reimplement each non-abstract methods (DRY)? What if the _interface_ gets a new method with body in time? I would have to find all it's implementations and correct it. This wouldn't be the case with abstract class. Please see my update to the question. – Maciej Sz Nov 02 '14 at 15:43
  • Ad 2 - ok I got little confused, somehow I thought that having `const Stack()` constructor would prevent me from having non-const `_StackImpl()` constructor, but it worked (see update). Ad 3 - what do you mean not all fields are final? There are only two fields in `_StackImpl` and they are both final. Base class has no fields, only abstract getters. Do they count as non-final fields? – Maciej Sz Nov 02 '14 at 15:44
  • Sorry this was an oversight. I missed that these are just getters. – Günter Zöchbauer Nov 02 '14 at 19:33