Why duck typing is allowed for classes in TypeScript

Asked
Active3 hr before
Viewed126 times

7 Answers

typing
90%

Stack Overflow for Teams Where developers & technologists share private knowledge with coworkers ,Thanks for contributing an answer to Stack Overflow!,It is now possible to create nominal types with TypeScript that allow you to discriminate types by context. Please consider the following question:,Stack Overflow en español

Your problem will however only manifest for trivial classes, as soon as you add privates, classes become incompatible even if they have the same structure:

class Vehicle {
   private x: string;
   public run(): void {
      console.log('Vehicle.run');
   }
}

class Task {
   private x: string;
   public run(): void {
      console.log('Task.run');
   }
}

function runTask(t: Task) {
   t.run();
}

runTask(new Task());
runTask(new Vehicle()); // Will be a compile time error

This behavior also allows you to not explicitly implement interfaces, for example you function could define the interface for the parameter inline, and any class that satisfies the contract will be compatible even if they don't explicitly implement any interface:

function runTask(t: {
   run(): void
}) {
   t.run();
}

runTask(new Task());
runTask(new Vehicle());
load more v
88%

One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.,One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a class meets a particular contract, is also possible in TypeScript.,Everyday TypesAll of the common types in TypeScript,All of the common types in TypeScript

The easiest way to see how interfaces work is to start with a simple example:

tsfunction printLabel(labeledObj: {
   label: string
}) {
   console.log(labeledObj.label);
}
let myObj = {
   size: 10,
   label: "Size 10 Object"
};
printLabel(myObj);
load more v
72%

With duck typing, Typescript compares signatures of classes and allows object of one type to be used with an instance of another if the object’s type signature is same as, or is a subset of, the initiating class’s signature.,How would you ensure that a new object can interact in a type-safe way with a class and another object under these circumstances?,Typescript does not care about the initializing class as long as it has all the props and methods of the class used for the type. So, keep calm and carry on typing.,Now, also mix this background of a problem with an additional complexity. Typescript strives to be compatible with non-typed objects within Typescript and variables from Javascript.

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
load more v
65%

Looks like in TypeScript it's absolutely fine (from the compiler perspective) to have such code:,But at the same time I would expect a compilation error, because Vehicle and Task don't have anything in common.,And yes, for JavaScript it's absolutely fine to have such behaviour, because there is no classes and no compiler at all (only syntactic sugar) and duck typing is natural for the language. But TypeScript tries to introduce static checks, classes, interfaces, etc. However duck typing for class instances looks rather confusing and error-prone, in my opinion.,Your problem will however only manifest for trivial classes, as soon as you add privates, classes become incompatible even if they have the same structure:

Looks like in TypeScript it's absolutely fine (from the compiler perspective) to have such code:

class Vehicle {
   public run(): void {
      console.log('Vehicle.run');
   }
}

class Task {
   public run(): void {
      console.log('Task.run');
   }
}

function runTask(t: Task) {
   t.run();
}

runTask(new Task());
runTask(new Vehicle());

And sane usages can be implemented via explicit interface definition:

interface Runnable {
   run(): void;
}

class Vehicle implements Runnable {
   public run(): void {
      console.log('Vehicle.run');
   }
}

class Task implements Runnable {
   public run(): void {
      console.log('Task.run');
   }
}

function runRunnable(r: Runnable) {
   r.run();
}

runRunnable(new Task());
runRunnable(new Vehicle());

... or a common parent object:

class Entity {
   abstract run(): void;
}

class Vehicle extends Entity {
   public run(): void {
      console.log('Vehicle.run');
   }
}

class Task extends Entity {
   public run(): void {
      console.log('Task.run');
   }
}

function runEntity(e: Entity) {
   e.run();
}

runEntity(new Task());
runEntity(new Vehicle());
load more v
75%

JavaScript class, TypeScript interface, duck-typing, anonymous object,I think it might be useful if TypeScript didn't always treat classes same as interfaces. For example, I'd like to see at least a warning when assigning a compatible anonymous object to a variable of a known class type, like this (Playground link):,Using Object.assign fixes the validation, but now TypeScript assignment for object literal is broken and this reports no compilation errors:,It would be better if typescript distinguishes between an type based of a typescript Interface description and type of a class

class C {
   s: string = "";
}
const o: C = {
   s: "123"
};
console.log(o.s);
console.log(o instanceof C); // false
console.log(o.constructor.name); // Object
load more v
40%

There are many benefits that come with interface. Think of interfaces a shape. An interface defines the values within the object (not an instance of a class).,Apply the defined Episode interface within the App.tsx file:,Thus interface help enforce consistency for these objects and helps with performance.,The biggest difference between a class and an interface is that a class provides an implementation, not just its shape.

Union, the declared type can be one of many types.

type flag = true | false;
type myNumbers = 2 | 6 | 12;

Generics, usually used in arrays. It will declare all values within the array with the provided type.

type testScores = Array<number>;
load more v
22%

Interface is a structure that defines the contract in your application. It defines the syntax for classes to follow. Classes that are derived from an interface must follow the structure provided by their interface.,The TypeScript compiler does not convert interface to JavaScript. It uses interface for type checking. This is also known as "duck typing" or "structural subtyping".,Of course, the implementing class can define extra properties and methods, but at least it must define all the members of an interface.,Similar to languages like Java and C#, interfaces in TypeScript can be implemented with a Class. The Class implementing the interface needs to strictly conform to the structure of the interface.

interface IEmployee {
   empCode: number;
   empName: string;
   getSalary: (number) => number; // arrow function
   getManagerName(number): string;
}
load more v

Other "typing-undefined" queries related to "Why duck typing is allowed for classes in TypeScript"