Classes

A way of organizing objects

In Javascript it’s easy to make objects

const p = { x: 10, y: 20 };

We can write functions to make objects

const point = (x, y) => {
  return { x: x, y: y };
}

const p = point(10, 20);

And functions that do things with those objects

const distance = (p1, p2) => {
  return Math.hypot(p1.x - p2.x, p1.y - p2.y);
}

Using them

const origin = point(0, 0);
const p = point(10, 20);
console.log(distance(p, origin));

With classes we can attach behavior to objects

Behavior attached to objects are called “methods”.

Methods are like functions but are defined within the class.

Within methods the magic variable this references the object on which the method was invoked.

Defining a Point class.

class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  distanceTo(other) {
    return Math.hypot(this.x - other.x, this.y - other.y);
  }
}

Now

const origin = new Point(0, 0);
const p = new Point(10, 20);
console.log(p.distanceTo(origin));

We say we’re invoking distance on p.

(And passing origin as the argument.)

So what's the point?

(Ooof, no pun intended.)

That’s nice and all, but doesn’t seem like that big a win.

Polymorphism!

From Greek: “many shaped”

The classic example

class Circle {
  constructor(x, y, r, color) {
    this.x = x;
    this.y = y;
    this.radius = r;
    this.color = color;
  }

  draw(ctx) {
    // a bunch of gorp to draw a circle
  }
}

Rectangle

class Rectangle {
  constructor(x, y, width, height, color) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = color;
  }

  draw(ctx) {
    // a bunch of gorp to draw a rectangle
  }
}

We can make an array containing different kinds of shapes.

const shapes = [
  new Circle(10, 20, 30, 'black'),
  new Rectangle(20, 30, 30, 40, 'black'),
  // ...
]

Now we can draw them without knowing which ones are which kind

shapes.forEach(s => s.draw(ctx));

Recursive data structures

Recursion and classes: two great tastes that go great together!

Let’s represent mathematical expressions

This is based on something Sze Ting is working on where he wants to generate LaTeX (a typesetting language) from mathematical expressions.

One class

class Number {
  constructor(value) {
    this.value = value;
  }
  toLaTeX() {
    return this.value.toString();
  }
}

Another class

class Addition {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  toLaTeX() {
    return this.left.toLaTeX() + "+" + this.right.toLaTeX();
  }
}

And one more

class Subtraction {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  toLaTeX() {
    return this.left.toLaTeX() + "-" + this.right.toLaTeX();
  }
}

Using them

const expr = new Addition(new Number(2), new Number(3));
const expr2 = new Subtraction(expr, new Number(10));
console.log(expr2.toLaTeX());

Abstract Syntax Trees

These classes are defining something called an Abstract Syntax Tree: a tree of objects that represent the structure of some language, usually for the purpose of transforming that language into another language.

Inheritance

A way of organizing classes and removing a certain amount of duplicate code.

The parent class

First we define a “base” or “parent” class.

class BinaryOperation {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }
}

Child classes

Child classes “extend” a parent class and “inherit” behavior and structure from that class.

class Addition extends BinaryOperation {
  toLaTeX() {
    return this.left.toLaTeX() + "+" + this.right.toLaTeX();
  }
}

class Subtraction extends BinaryOperation {
  toLaTeX() {
    return this.left.toLaTeX() + "-" + this.right.toLaTeX();
  }
}

Double dispatch

Method dispatch allows us to use the type of the object we invoke the method on to control what code runs.

This is called single dispatch.

But sometimes we want to determine what code to run based on the types of two objects.

For instance …

Once we’ve got our mathematical expression AST fleshed out, we may want to do something else with it than just generate LaTeX.

We might want to generate some other kind of output. Or we might want to evaluate the expressions.

It’d be nice if we didn’t have to clutter up our AST classes with code for all those different things.

So let’s split out the LaTeX code

class LaTeX {
  number(expr) { return expr.value.toString(); }

  addition(expr) {
    return this.gen(expr.left) + "+" + this.gen(expr.right);
  }

  subtraction(expr) {
    return this.gen(expr.left) + "-" + this.gen(expr.right);
  }

  gen(ast) { ast.visit(this); }
}

Updating the AST classes

class Number {
  visit(generator) {
    return generator.number(this);
  }
}

Continued

class Addition extends BinaryOperation {
  visit(generator) {
    return generator.addition(this);
  }
}

class Subtraction extends BinaryOperation {
  visit(generator) {
    return generator.subtraction(this);
  }
}

Let's walk through what this does.

const expr = new Addition(new Number(2), new Number(3));
const expr2 = new Subtraction(expr, new Number(10));
new LaTeX().gen(expr2);

The payoff

Now we can easily add a new “backend” to generate some other language or do something else with our AST by writing a new class to replace LaTeX without having to change the AST classes.

For instance

class Evaluator {
  eval(ast) {
    ast.visit(this);
  }
  number(expr) {
    return expr.value;
  }
  addition(expr) {
    return this.eval(expr.left) + this.eval(expr.right);
  }
  subtraction(expr) {
    return this.eval(expr.left) - this.eval(expr.right);
  }
}