3

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.

1 Answer 1

4

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) {
    ...
}
Sign up to request clarification or add additional context in comments.

4 Comments

Okay, but haven't I specified a type constraint on candidate? In MyExtractor, T is MyClass, so why is candidate allowed to be anything else? Indeed, if I assert that the type of candidate is string and return an empty string, I get the error I expect: "Type '(candidate: string) => string' is not assignable to type '(candidate: MyClass) => MyClass'. Types of parameters 'candidate' and 'candidate' are incompatible. Type 'MyClass' is not assignable to type 'string'."
Check my revised answer
It would be nice if TS could contextually type process(candidate) to process(candidate: MyClass): MyClass in this case. I've faced similar confusion with how React component definition specifies a type for members, but you can actually implement with incorrect types, its still up to you to give it the correct type in your implementation.
@Aaron The fact that the interface declares that the method expects a MyClass param doesn't necessarily mean that the implementation can't handle other things, which is why the compiler won't make that assumption. To avoid this "problem" simply use noImplicitAny, then you'll know when/where you have these sort of cases.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.