ethan.roday
ethan.roday

Reputation: 2635

Typescript: Implementing a generic interface

Consider the following generic interface:

interface Extractor<T> {
  extractCandidate(): T;
  process(candidate: T): T;
}

Conceptually, each Extractor implementation is responsible for extracting a particular kind of object from some datasource. The consumer of the Extractor can extract a candidate object using extractCandidate() or, given any candidate, perform some further processing on it to get a more refined version of the object using process().

Now, let's say I implement an Extractor for a class MyClass, like so:

class MyClass {
  a: string;
  b: string;
}

class MyExtractor implements Extractor<MyClass> {
  extractCandidate() {
    //Some dummy logic
    return new MyClass();
  }
  process(candidate) {
    //Some dummy logic
    let res = new MyClass();
    res.a = candidate.a;
    return res;
  }
}

Now let's say I instantiate MyExtractor and use it:

let myExtractor = new MyExtractor();
let processed = myExtractor.process('blah');

Question 1: Why does this not generate a compile-time error? Based on the definition of the Extractor interface, I would expect that the compiler would not allow me to call myExtractor.process() with anything but an instance of MyClass, or at least with something that is structurally compatible.

Question 2: How can I enforce the desired behavior? Do I just need to assert that the candidate parameter of MyExtractor.process() is of type MyClass?

I suspect this has something to do with TypeScript's structural typing system but, after reading some related questions and the FAQ, I'm still not sure how it specifically applies here.

My Typescript version is 2.1.4.

Upvotes: 3

Views: 6995

Answers (1)

Nitzan Tomer
Nitzan Tomer

Reputation: 164129

The code you posted for MyExtractor has the following signature for the process method:

process(candidate: any): MyClass

The reason for that is that you haven't specified a type for candidate so by default it is any.
The compiler won't complain because it satisfies candidate: T (as any can be T).

If you change your code to:

process(candidate: MyClass) {
    ...
}

Then for:

let processed = myExtractor.process('blah');

You'll get:

Argument of type '"blah"' is not assignable to parameter of type 'MyClass'

You can avoid that by using the --noImplicitAny flag which will cause the compiler to complain about:

process(candidate) {
    ...
}

Saying:

Parameter 'candidate' implicitly has an 'any' type


Edit

Candidate isn't allowed to be "anything else", it is allowed to be any (and that's the default), a good reason for that for example is for overloading:

process(candidate: string): MyClass;
process(candidate: MyClass): MyClass;
process(candidate: any) {
    ...
}

Upvotes: 4

Related Questions