How to use the switch statement when checking whether some object is an instance of some class.

If you don’t like long reading, skip to solutions: constructor based and DSL based.

Imagine code like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Shape {}
class Point extends Shape {}
class Rectangle extends Shape {}
class Circle extends Shape {}

const shape = new Circle();

if (shape instanceof Point) {
    console.log('It is a point!');
} else if (shape instanceof Rectangle) { 
    console.log('It is a rectangle!');
} else if (shape instanceof Circle) { 
    console.log('It is a circle!');
}

This code works just fine and as expected (try it). But isn’t there a better way? Without multiple if statements? This is where the switch statement comes to play. We usually use the switch statement when we have a few fixed amounts of values and we want to do different things for different values. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
enum Color {red, green, blue};

function printColorName(colorCode: Color): void {
    switch (colorCode) {
        case Color.red:
            return console.log('red');
        case Color.green:
            return console.log('green');
        case Color.blue:
            return console.log('blue');
    }
}

printColorName(Color.green);

The problem with the shape instanceof Point is that it cannot be simply transformed into the switch statement. This does not work:

1
2
3
4
switch (shape) {
	case Point:
		console.log('It is a point!');
}

Why? The interpreter would simply check whether the shape variable equals the Point variable and that is not the case. It does not check if shape is an instance of Point. It checks if shape is Point.

Use .constructor

There is a hack. You can check whether the constructor property equals the class. The constructor property returns a reference to the Object constructor function that created the instance object. Roughly speaking, it contains a reference to the Class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Shape {}
class Point extends Shape {}
class Rectangle extends Shape {}
class Circle extends Shape {}

const shape = new Circle();

switch (shape.constructor) {
    case Point:
        console.log('It is a point!'); break;
    case Rectangle:
        console.log('It is a rectangle!'); break;
    case Circle:
        console.log('It is a circle!'); break;
	default:
        console.log('Unknown object...');
}

It works! (try it) But there is a significant drawback:

It checks if shape’s constructor equals the given class. It does not check if it is an instance of this class – it does not work well with “inheritance”. If we add another shape called RedCircle that extends Circle, the switch statement fails to recognize it as a Circle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Shape {}
class Point extends Shape {}
class Rectangle extends Shape {}
class Circle extends Shape {}
class RedCircle extends Circle {}

const shape = new RedCircle();

switch (shape.constructor) {
    case Point:
        console.log('It is a point!'); break;
    case Rectangle:
        console.log('It is a rectangle!'); break;
    case Circle:
        console.log('It is a circle!'); break;
    default:
        console.log('Unknown object...');
}

It prints “Unknown object…”. (try it)

Not that TypeScript does infer the type. Let’s add property radius to the Circle class. We should be able to read the property is we check the shape is actually a Circle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Shape {}
class Circle extends Shape {
    public constructor(public readonly radius: number) {
        super();
    }
}

const shape = new Circle(42);

switch (shape.constructor) {
    case Circle:
        console.log(`It is a circle with radius=${shape.radius}`); break;
    default:
        console.log('Unknown object...');
}

And it works (try it).

Write Your Own Mini-DSL

Maybe switch syntax is not the answer. Maybe no syntax is the answer. Maybe mini-DSL is the answer. DSL stands for domain-specific language. We can simply ignore the switch statement and we can even ignore the if statement. We can write our own function with some nice API to check whether the shape variable is Circle or not. What would you say to this syntax:

1
2
3
4
5
6
const shape = new RedCircle("MyShinyShape");

givenShape(shape)
    .ofType(Point).do(shape => console.log(`It is a point named ${shape.name}`))
    .ofType(Rectangle).do(shape => console.log(`It is a rectangle named ${shape.name}`))
    .ofType(Circle ).do(shape => console.log(`It is a circle named ${shape.name}`));

Is it readable? I’d say yes. Don’t like the method names? Use any better naming you like! That is the magic. And how to implement it? I usually write it using a few nested functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
interface ConstructorOf<CLASS> {
    new (...args: ReadonlyArray<never>): CLASS;
}

function givenShape(shape: Shape) {
    const actions = {
        ofType<T extends Shape>(ShapeType: ConstructorOf<T>) {
            return {
                do(fn: (shape: T) => void) {
                    if (shape instanceof ShapeType) {
                        fn(shape);
                    }

                    return actions;
                }
            }
        }
    };

    return actions;
}

If it gets too big, I refactor it (try it). Why do I like it?

  • It reads nicely!
  • I can implement any logic I want. With the switch statement I cannot implement the proper instanceof logic. This way? No problem.
  • I can use domain-specific names of the methods. The method do is not exactly domain-specific, but we could use something like drawOnCanvas to be more specific.
  • We can implement additional logic there.

What are the drawbacks? As far as I know, it’s not possible to implement lazy evaluation: all of the conditions are always evaluated. That should not be an issue in most cases.

Do you have any better ideas?

Further reading