Skip to content
This repository was archived by the owner on Jul 9, 2022. It is now read-only.

Classical Inheritance

Aadit M Shah edited this page Feb 25, 2014 · 2 revisions

Since augment is primarily a classical inheritance pattern let's create some classes. We'll start by implementing the EventStream class which is personally my favorite class:

var EventStream = augment(Object, function (concat) {
    this.constructor = function () {
        this.listeners = [];
    };

    this.set = function (a) {
        var listeners = this.listeners;

        setTimeout(function () {
            listeners.forEach(function (listener) {
                listener(a);
            });
        }, 0);

        return this;
    };

    this.get = function (f) {
        this.listeners.push(f);
        return this;
    };

    this.map = function (f) {
        var stream = new EventStream;

        this.get(function (a) {
            stream.set(f(a));
        });

        return stream;
    };

    this.filter = function (f) {
        var stream = new EventStream;

        this.get(function (a) {
            if (f(a)) stream.set(a);
        });

        return stream;
    };

    this.scan = function (a, f) {
        var stream = new EventStream;

        setTimeout(function () {
            stream.set(a);
        }, 0);

        this.get(function (b) {
            stream.set(a = f(a, b));
        });

        return stream;
    };

    this.merge = function () {
        var stream = new EventStream;

        concat.apply([this], arguments).forEach(function (that, index) {
            that.get(function (a) {
                stream.set({
                    from: index,
                    data: a
                });
            });
        });

        return stream;
    };
}, Array.prototype.concat);

That's a lot of code for a tutorial. However it's actually really easy to understand. I'll begin by elucidating the constructor method:

constructor :: ()            // The constructor takes no arguments.
            -> EventStream a // It returns an `EventStream` object of events of type `a`.

Next we have the set method:

set :: a             // Take a value of type `a` and update the value of the event stream.
    -> EventStream a // Return the original event stream to allow chaining of operations.

Then we have the get method:

get :: (a -> b)      // Take a function of type `a -> b` and send it updates of the event stream.
    -> EventStream a // Return the original event stream to allow chaining of operations.

Now comes the interesting part. The map method allows you to transform an event stream:

map :: (a -> b)      // Take a function of type `a -> b` and send it events of type `a`.
    -> EventStream b // Return an `EventStream` that for every event of type `a` fires an event of type `b`.

Next we have the filter method that creates a new EventStream of selected events:

filter :: (a -> Bool)   // Take a predicate function of type `a -> Bool` and send it events of type `a`.
       -> EventStream a // Return an `EventStream` of events of type `a` which are `true` under the predicate.

Then we have the scan method which creates a new EventStream by accumulating events:

scan :: b             // Take an initial value of type `b`. This is the first value of the new event stream.
     -> (b -> a -> b) // Take a function and send it the previous value of type `b` and the event of type `a`.
     -> EventStream b // Return an `EventStream` of events of type `b` generated by the above function.

Finally we have the merge method which merges multiple events streams into one:

merge :: EventStream a1..EventStream aN // Take multiple event streams and merge them with `this`.
      -> EventStream { from :: 0..N     // Return an `EventStream` of events tagged with "from stream" indices.
                     , data :: a0..aN   // Each new event has the data of the original event.
                     }

Awesome. Now that that's out of the way let's talk about inheritance.

Timer Stream

Event streams come in different flavors. One of the most common type of event stream is a timer stream.

A timer stream periodically emits events. It can be paused and resumed at any time. It's useful for creating animations.

var TimerStream = augment(EventStream, function (uber) {
    this.constructor = function (interval) {
        uber.constructor.call(this);
        this.interval = interval;
    };

    this.start = function () {
        var self = this, now = Date.now();

        self.timeout = setTimeout(function () {
            loop.call(self, now);
        }, 0);

        return now;
    };

    this.stop = function () {
        clearTimeout(this.timeout);
        return Date.now();
    };

    function loop(time) {
        var self = this, now = Date.now(), next = time + self.interval;

        self.timeout = setTimeout(function () {
            loop.call(self, next);
        }, next - now);

        self.set(now);
    }
});

As you can see creating derived classes using augment is dead simple. Every class has a function body. Using a function as the class body has the following advantages:

  1. It eliminates the need for a for..in loop to copy the methods of the class.
  2. It allows augment to easily pass values to the class (e.g. concat and uber).
  3. It allows you to easily create private static data members (e.g. loop).

In the above TimerStream class we use the uber object passed to the class function body to call the base class constructor from the derived class constructor. The values passed to the class function body are:

  1. All the arguments after the base class and the function body passed to the augment function.
  2. The prototype object of the base class. Since it's always at the end of the argument list you can leave it out if you don't need it (as we left it out in the EventStream class).

Using uber you can call any method of the base class even if it's been overridden by the derived class. Here we used uber.constructor.call(this) to call the base class constructor from the derived class.

Conclusion

To recap we learned the following about classical inheritance using augment:

  1. Creating a derived class is as simple as passing the base class as the first argument to augment.
  2. The last argument passed to the function passed to augment is the prototype of the base class.
  3. The prototype of the base class can be used to call overridden base class methods.

That's all you will ever need to know about classical inheritance using augment.

Clone this wiki locally