Timmmm
Timmmm

Reputation: 96556

Redundant function definitions in Typescript

I came across this code VSCode:

export class HistoryService extends Disposable implements IHistoryService {
    ...

    remove(input: IEditorInput | IResourceEditorInput): void;
    remove(input: FileChangesEvent): void;
    remove(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void {
       ...
    }

What is the point of the first two remove() definitions? Aren't they a subset of the last line?

Upvotes: 1

Views: 154

Answers (2)

kaya3
kaya3

Reputation: 51034

There is a semantic difference between a function with overloads vs. a function with a parameter having a union type. Consider the following code:

interface A { a: number }
interface B { b: number }

// overloads
function f(a: A): void;
function f(b: B): void;
function f(ab: A | B): void {
    // ...
}

// union type parameter
function g(ab: A | B): void {
    // ...
}

let ab: A | B = Math.random() > 0.5 ? { a: 1 } : { b: 2 };
f(ab); // error
g(ab); // no error

Playground Link

In this code, f can be called with an argument of type A or an argument of type B, but not an argument of type A | B, because A | B does not conform to either overload signature. On the other hand, g can be called with an argument of type A | B because that exactly matches its parameter type.

So in your example, the reason for using overloads may be to signal that an argument should not have the type IEditorInput | IResourceEditorInput | FileChangesEvent, perhaps because the caller is expected to know whether they are calling the function with an "input" or an "event".

Upvotes: 4

T.J. Crowder
T.J. Crowder

Reputation: 1074218

They're function overloads. The first two signatures are the public signatures of the function; the third is its implementation.

In the general case, they can make it much clearer what a function is expecting. It's true that in the specific case you've shown in the question, you could just write the third signature; the overloads aren't buying you anything. But perhaps doing it this way makes the intent clearer when auto-completion options are shown, etc.

Here's a better example of where overloads help clarity:

interface OptionsObject {
    foo: string;
    bar: number;
    somethingElse: Date;
}
function example(foo: string, bar: number, somethingElse: Date): void;
function example(options: OptionsObject): void;
function example(arg0: string | OptionsObject, bar?: number, somethingElse?: Date): void {
    // ...implementation...
}

Playground link

The overloads make it clear that you can call that function in one of two ways:

  • With a string, number, and Date; or
  • With an object that has properties for those things.

In contrast, the implementation signature doesn't make it clear that calling it with just a string argument and nothing else isn't valid. To make that clear, you need the overloads.

But again, not with the example you cited. It's not clear to me why overloads were used there (unless the code used to be different and has changed over time).

Upvotes: 3

Related Questions