Class Brands and Type-Checking Weirdness

Topics: Language Specification
Oct 4, 2012 at 4:11 PM

As I started to write this post on how I don't like class "brands" as described in the language spec, I discovered that the implementation in the Visual Studio 2012 plugin does not appear to follow the language spec. Here's the problematic code:

class Duck {
    constructor (public name : string) { };
class Dog {
    // age: number; // Uncomment this line to see an error
    constructor (public name : string) { };
function quack(duck: Duck) {
var fido = new Dog('fido');
var rufus = {
    name: 'rufus'
   // , age: 7 // Uncomment this line to see an error 
quack(new Duck('penelope'));

On my machine the code above compiles without error.

This would seem to be in violation of the language spec (p21 and p29), although I'm happy that this compiles without error.Unfortunately uncommenting either of the marked lines triggers a compile error. I would expect (based on desire, not the language spec) that the code would compile regardless of whether the commented lines remain commented or are uncommented.

Ignoring the implementation for now, a couple of the problematic areas of the language spec are this comment on p21

"For example, two classes with exactly the same member declarations are still not compatible because each class has a distinct brand"

and this part of the assignment compatibility section on p29

"M is a brand and S contains the same brand"

Briefly stated, I don't see enough value in checking the class brand on assignment using the current syntax. In strongly typed languages like C#, we see a lot of value by preferring abstract types (interfaces) over concrete types (classes). And in weakly typed languages like Javascript, we see a lot of value in duck typing for code reuse. These features are important for application-scale coding and branding classes in TypeScript is a move in the wrong direction - it's promoting overuse of concrete types and it makes class syntax much less useful because it will force users to define a separate interface that they can use in function signatures so they can avoid the brand check. This means that users will have to repeat themselves (same properties on the interface and on the class) early in development in order to avoid painting themselves in to a corner later. This also leads to complexity in teaching the language.

I would prefer 'class Duck' syntax to be entirely equivalent to defining two interfaces ('interface Duck' for the instance members and 'interface Duck$class' for the statics and constructors) and one variable (to which the function object is assigned). Class syntax is nice - constructor functions with public parameters are great - so it would be good to be able to use 'class' to define and implement these interfaces in one go. I can't do that as long as brand checks happen on assignment by default.

Related to these discussions, can you provide us some kind of guidance in terms of what work you have planned and what contributions you are likely to accept from the community. Thanks.

Oct 5, 2012 at 12:15 AM
Edited Oct 5, 2012 at 12:23 AM

If I'm not mistaken, this looks very similar to what I proposed 

and unfortunately there are no answers so far.

I fully support this idea - while unconventional (its not "strict enough" compared to other strongly typed languages), its by far a lot more useful and practical. Make mock objects, create completely new implementations that replace old implementations - without inheriting, without dependency injection, and without writing ("extracting") boilerplate interface code.

And if you really absolutely must have strict class checking you can use a more verbose syntax: class SpecificClass { SpecificClass: bool; }. or even have first-class language-level support, something like "branded class SpecificClass".

But non-strictness should be the default. Because sadly us developers often fail to foresee the need to use an interface instead of a class.

Oct 5, 2012 at 4:39 AM

Yes, that's right- we both want the same thing. In fact, this section on page 8 of the spec's introduction strongly implies that at least one of the authors of the spec was thinking along the same lines:


This TypeScript class declaration creates a variable named ‘BankAccount’ whose value is the constructor function for ‘BankAccount’ instances. This declaration also creates an instance type of the same name. If we were to write this type as an interface it would look like the following.

       interface BankAccount {
           balance: number;
           deposit(credit: number): number;

If we were to write out the function type declaration for the ‘BankAccount’ constructor variable, it would have the following form.

var BankAccount: new() => BankAccount; 


If this were how it worked, we'd be happy.

Oct 5, 2012 at 4:24 PM

This appears to be a bug in the compiler. According to the spec, only Duck and subclasses of Duck are assignable to Duck because other types would lack Duck's unique brand.

Bug aside, the reasoning for this behavior is that classes represent more that just their public interface. Classes also contain internal state and implementation and something that happens to have the same public interface very likely won't have the appropriate internal state and implementation. In a sense the brand represents that state and implementation and ensures something incompatible isn't accidentally substituted.

That said, I actually quite sympathize with your views. We've gone back and forth on brands in the design group and landed on the more conservative view, but there is definitely something to be said for having class instance types just be equivalent to interfaces.

Oct 5, 2012 at 7:17 PM
Edited Oct 5, 2012 at 9:21 PM

Thanks for the response. Did you consider having additional syntax to trigger the brand check?

How about an implementation where defining 'class Duck' corresponds to:


interface Duck {} // Contains instance properties

interface Duck$ extends Duck { $Duck$brand: string; } // Branded version of instance interface

(plus an interface for the statics and constructors)

That way when people define a function using 'Duck', the assignment check is non-branded:

function myFunction(x : Duck) // Non-branded - x just has to look like a duck

And when people define a function using 'Duck$', the assignment check is branded:

function myFunction(x : Duck$) // Branded - x has to _be_ a Duck

This would be easy to explain and nice to use.


It would also allow the following code to compile:



interface Dog { age: number; }

interface Dog { name: string; }

var dog: Dog = null;

dog.age = 7; = 'Rufus';

class Duck { age: number; }

interface Duck { name: string; }

var duck : Duck = null;

duck.age = 7; = 'Penelope';

Currently interfaces are extensible while classes are not, so the code above involving ducks does not compile. Since my proposal is that 'class Duck' is shorthand for 'interface Duck' and 'interface Duck$', the code above which extends 'Duck' would compile under my proposal. I leave it unspecified whether the Duck$ interface would be extensible or not. Personally I would treat it like any other interface, so it would be extensible. But if you think the lack of extensibility of classes is an important feature, you could make the branded interface non-extensible and I'd still be happy as long as the non-branded interface is extensible.

Oct 6, 2012 at 10:20 AM

Or, if the defaults are already set-in-stone, at least have it as an option:

function f(a: {
    // f accepts Duck-like classes now is Duck's public interface.

Classes also contain internal state and implementation and something that happens to have the same public interface very likely won't have the appropriate internal state and implementation. In a sense the brand represents that state and implementation and ensures something incompatible isn't accidentally substituted

This is not very convincing, as its trivial to override all public methods using inheritance to make a completely different behavior (at least at the moment). But I do see how this can happen:

class Line {
    x1: number, y1: number, x2: number, y2: number;
    intersects: function(l: Line):bool {
        // code that calculates intersection between lines

class Rect {
    x1: number, y1: number, x2: number, y2: number;
    intersects: function(r: Rect):bool {
        // code that calculates intersection between rects

var l = new Line(); r = new Rect();

l.intersect(r) != r.intersect(l) // both calls make no sense yet they're still allowed.

Oct 8, 2012 at 6:50 PM

I appreciate your willingness to compromise, but I'd strongly prefer the non-branded interface to be the default. This decision is pretty central to defining how TypeScript feels and how it relates to Javascript. The positioning so far is that TypeScript extends Javascript, not that it's a strongly-typed language that happens to compile down to Javascript. This is an area where that messaging will see some friction.

Having spent many years working in strongly typed languages where people constantly get bitten by overuse of concrete types, I firmly believe that it will benefit code quality and testability to have 'class X' produce a non-branded 'interface X'. There should be (minimal) extra typing if you want to use the branding. We've had interfaces in C++ and C# since forever, but people don't use them everywhere they should because they're not the 'default'. It'd be nice if TypeScript's default concept was 'interface' instead of 'class'. I recognize that TypeScript classes are less constraining than classes in other languages but there are still requirements on constructor calls and on the types of overriding members.

Oct 8, 2012 at 9:41 PM

I'm coming around to the point of view that a class should just define and implement a non-branded interface, and that we should simply get rid of brands. A strong argument is that writing a separate implementation of the interface really is equivalent to overriding all of the public methods on the class. Classes with private members will still appear to be branded because it is not possible to separately implement the private members. Effectively, you'd be able to substitute entirely separate implementations of the interface only if there are no private members in the class.

Oct 8, 2012 at 10:55 PM
Edited Oct 8, 2012 at 11:07 PM

That's certainly promising news. I regret not fully describing public/private in my original proposal. I'll clarify my proposal so that 'class X' corresponds to these 3 interfaces:

  1. interface X contains _public_ instance members
  2. interface X$ extends X and adds _private_ instance members (and the brand if it continues to exist)
  3. interface X$class contains _public_ statics/constructor
I assert that private members should not be present on the main interface. The aim of the proposal is to allow assignment from anything that implements the _public_ interface of 'class X' to a parameter typed as 'X'.
In the case where private members are present on the main interface, consider particularly the undesirable consequence of adding the first private member to a class in an existing code base. Private members are 'implementation details', but adding a single private member could cause unrelated code to fail to compile if 'X' means 'public and private members of X'. The majority of uses of 'X' will be in functions outside of class X. Private members are inaccessible in that context; yet the addition of a private member would cause that code to fail the assignment compatibility check. A function with parameter of type X defined outside class X doesn't and can't use private members of X, yet the compiler would complain if the argument couldn't supply those private members. For that reason and others I think it's important to separate the public interface from the rest.
It's true that within class X a developer might want to be sure that they are accessing instances of class X so that they can access private members. In that case, under my proposal, they would use 'X$' as their parameter type (or in a cast) to get access to the private members.
BTW under my proposal I believe the type of 'new X()' should be X (public only), not X$ (public and private). But I also believe that decision can be made separately from the decision about whether 'X' represents the public interface only or both public and private. (Although the more experience I get with TypeScript's type inference, the more convinced I am that the type of 'new X()' should be the public interface of X).