E
E
Evgeny Zhurov2021-12-27 19:04:19
typescript
Evgeny Zhurov, 2021-12-27 19:04:19

Why is function overloading needed?

I do not really understand the expediency of using overload.
The tutorial says that this is necessary so that the function can accept different types of data and return different types of data. And this is an example:

type Reserve = {
  (from: Date, to: Date, destination: string): string
  (from: Date, destination: string)
}

const reserve: Reserve = (
  from: Date,
  toOrDestination: Date | string,
  destination?: string
) => {
  if (toOrDestination instanceof Date && destination !== undefined) {
    return 'Round way ticket'
  } else if (typeof toOrDestination === 'string') {
    return 'One way ticket'
  }
}


This is a type of ticket order with the option not to take a return ticket. But is this sheet better than, say, such a record?
type Reserve2 = (from: Date, destination: string, to?: Date) => string

const reserve2: Reserve2 = (from, dest, to) => (
  !to ? 'One way ticket' : 'Round way ticket'
)


Elsewhere I saw such an example, they say, 'a' can be both a number and a string
type SumOrConcat = {
  (a: number, b: number): number
  (a: string, b: number): string
}

const result: SumOrConcat = (a: any, b: any): any => {
  if (typeof a === 'number') {
    return a + b
  }

  return `${a} ${b}`
}


But it's quite a game, and even with the use of any. Isn't it better to do it this way?
type SumOrConcat = (a: number | string, b: number) => number | string

const result: SumOrConcat = (a, b) => (
  typeof a === 'number' ? a + b : `${a} ${b}`
)


I came up with all sorts of other examples myself, but everywhere it turns out what can be done without overloads. What are the situations where overload is really necessary?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Dmitry Belyaev, 2021-12-28
@Zhuroff

Overloads are needed in the following cases:
1. When the type of the returned value depends on the type of some argument;
2. When the types of subsequent arguments depend on the type of some argument;
3. Combination of 1 and 2 options.
It is important to understand that in TS overloads exist only at the type level, unlike in other languages. Moreover, the overloads themselves are implemented as an intersection from all signatures, which sometimes causes problems with assigning a function to a type with an overloaded signature. It's all about variance, since the function arguments are contravariant to the signature of the function itself, when most type relations (including intersection) in TS are covariant. In your SumOrConcat example, this problem was solved by using the any type, which makes any composition of types with it bivariant.
In fact, in the example with SumOrConcat, it is quite possible to set types to the arguments:

type SumOrConcat = {
  (a: number, b: number): number
  (a: string, b: number): string
}

const result: SumOrConcat = (a: number | string, b: number): any => {
  if (typeof a === 'number') {
    return a + b
  }

  return `${a} ${b}`
}
But it's safer like this:
type SumOrConcat = {
  (a: number, b: number): number
  (a: string, b: number): string
}

const result = ((a: number | string, b: number): number | string => {
  if (typeof a === 'number') {
    return a + b
  }

  return `${a} ${b}`
}) as SumOrConcat;
Well, it should be noted that the syntax of the overloads themselves does not have such problems, although you cannot describe an arrow function with it
function sumOrConcat(a: number, b: number): number;
function sumOrConcat(a: string, b: number): string;
function sumOrConcat(a: number | string, b: number): number | string {
  if (typeof a === 'number') {
    return a + b
  }

  return `${a} ${b}`
}

const result = sumOrConcat;

Also, very often you can do without overloading using generics. The code itself will look more complicated, but it will be easier to use. Example .

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question