Is it possible to perform an overload (Overload) with Arrow functions?

Asked

Viewed 64 times

4

I have a function where the return type depends on the parameter type. I was able to declare this overload with function, but not with Arrow Function, because I get the error:

Cannot redeclare block-scoped variable 'parseToDate2'.
'=>' expected.

See an example below:

// Funciona
function parseToDate<T extends string | null>(date: T): T extends string ? Date : null;
function parseToDate(date: string | null): Date | null {
  return typeof date === 'string' ? new Date(date) : null;
}

// Não funciona
const parseToDate2 = <T extends string | null>(date: T): T extends string ? Date : null;
const parseToDate2 = (date: string | null): Date | null => 
  typeof date === 'string' ? new Date(date) : null;

Playground

I am declaring the overload wrongly or it is not supported in Arrow functions? If not supported, why?

At first I figured how it works with function, should work somehow with Arrow Function.

  • 1

    Then it’s not because of const, which does not allow creating another variable with the same name?

  • @hkotsubo is, it seems to be that same . I did some tests with var, let and const and saw the MDN documentation, it makes sense. There is a different "notation" that allows the overload in Arrow functions? I found some answers on Soen but I could not adapt to the example of my question

2 answers

4


You will not be able to use the syntax "similar" to that of function statements since Typescript does not understand that multiple function statements via Arrow functions defined with const (or even let) are definitions of overloads.

For overload definitions using that more explicit notation, it is only with function declaration same. Refer to the documentation.

You have two alternatives:

  1. Do not use Arrow Function

    In this case, you simply go to the function declaration syntax. This is the way I prefer, since you don’t have much reason to use Arrow Function there. You will then be using the notation that the language has chosen for this.

    I’m not setting an example because there’s already a question.

  2. Create a new type (with defined overloads) and assign it to Arrow Function

    You have two ways to do this. Use interfaces or two function types joined by the type intersection operator, &.

    Example that uses interfaces:

    interface GetParent {
      (selector: string): HTMLElement | null;
      (element: HTMLElement): HTMLElement | null;
    }
    
    const getParent: GetParent = (selector) => {
      if (typeof selector === 'string') {
        return document.querySelector(selector)?.parentElement ?? null;
      }
    
      return selector.parentElement;
    }
    

    See on TSP.

    Example using intersection of function types:

    type GetParentViaSelector = (selector: string) => HTMLElement | null;
    type GetParentViaElement = (element: HTMLElement) => HTMLElement | null;
    type GetParent = GetParentViaSelector & GetParentViaElement;
    
    const getParent: GetParent = (selector) => {
      if (typeof selector === 'string') {
        return document.querySelector(selector)?.parentElement ?? null;
      }
    
      return selector.parentElement;
    }
    

    See on TSP.

I modified the examples to simplify the answer. : ) But generics also work.

I must point out that when the return types are different (as in the question example), the only way out is to use the explicit overload syntax itself with the function statements. Apparently it’s an expected behavior.

  • In this example of getParent it seems to me that it works because the return is always the same, and in the end the signature would be something like getParent(selector: string | HTMLElement) => HTMLElement | null. If the return is different, as in the question, it is not possible to do mode 2?

  • 1

    Yeah, @Rafaeltavares, I hadn’t tested this case, but it seems to me that with returns of incompatible types, you can’t use any other way than the function declaration syntax itself with the explicit overloads.

  • 1

    From the answers in the similar question on Soen I had not been able to. Solution 1 is great, at least for my situation. What I cared most about knowing was why I couldn’t Arrow functions and if he had another alternative. His answer was very precise in that.

1

Complementing the another answer, it is worth mentioning that in the end, the Typescript code is transposed into Javascript, which can help explain why it is not possible to do the Overload with Arrow functions.

If we use your code:

function parseToDate<T extends string | null>(date: T): T extends string ? Date : null;
function parseToDate(date: string | null): Date | null {
  return typeof date === 'string' ? new Date(date) : null;
}

To test I used the Deno, for with him you can easily get the Javascript file generated from Typescript. In the case of the above code, the generated Javascript was:

"use strict";
function parseToDate(date) {
    return typeof date === 'string' ? new Date(date) : null;
}

That is, a single function.

But I could also do so:

// só recebe uma string ou null
function parseToDate < T extends string | null > (date: T): T extends string ? Date : null;
function parseToDate(date: string | null): Date | null;
// recebe 3 números
function parseToDate(date: number, month: number, year: number): Date | null;

// implementação: ver mais em /a/418160/112052
function parseToDate(date: any, month?: any, year?: any): Date | null {
    if (typeof date === 'string')
        return new Date(date);
    if (typeof date === 'number' && typeof month === 'number' && typeof year === 'number')
        return new Date(year, month - 1, date);
    return null;
}

That the resulting Javascript would remain a single function:

function parseToDate(date, month, year) {
    if (typeof date === 'string')
        return new Date(date);
    if (typeof date === 'number' && typeof month === 'number' && typeof year === 'number')
        return new Date(year, month - 1, date);
    return null;
}

That is, all the overloads become a single function, as already explained in detail here.


And why with Arrow Function can’t?

With Arrow functions, you are actually creating a variable and assigning a function as its value. That is, if you do something like:

let func = (x: number): number => x + 1;
func = (x: number): number => x - 1;

// func contém somente a última arrow function
console.log(func(11)); // 10

In fact you are overwriting the value of the variable func with another value (with another function). The first function created is "lost", and in the end you only have the last one. The resulting Javascript is:

let func = (x) => x + 1;
func = (x) => x - 1;
console.log(func(11));

Notice that I had to put the function body and the "arrow" (=> x + 1 and => x - 1), because without it the code does not compile. That is, your example was incomplete.

And in your code, you used const, then gave error because it was trying to create the same variable twice.

But here’s the thing, I couldn’t do that:

let func = (x: number): number => x + 1;
// agora tem 2 parâmetros
func = (x: number, y:number): number => x + y;

This makes a mistake:

error: TS2322 [ERROR]: Type '(x: number, y: number) => number' is not assignable to type '(x: number) => number'.
func = (x: number, y:number): number => x + y;

Because initially the variable is of the type "function that receives a number and returns a number", but I tried to put in it a "function that gets two number's and returns a number", and as they are incompatible types, gives the error.

That is, once a variable has a value of Arrow Function, you could not set it with another function with different signature (which would also prevent the Overload - but even if that were possible, it wouldn’t actually be a Overload, because as I said, you would only be overwriting the value of the variable).

In that case, a way to have a Arrow Function accepting several parameters of different types (similar to a function overloaded) would declare the parameters in the most generic way possible, within the options you want to consider:

let parseToDate2 = (date: string | number | null, month?: number, year?: number): Date | null => {
    if (typeof date === 'string')
        return new Date(date);
    if (typeof date === 'number' && typeof month === 'number' && typeof year === 'number')
        return new Date(year, month - 1, date);
    return null;
};

Which turns into the following Javascript:

let parseToDate2 = (date, month, year) => {
    if (typeof date === 'string')
        return new Date(date);
    if (typeof date === 'number' && typeof month === 'number' && typeof year === 'number')
        return new Date(year, month - 1, date);
    return null;
};

That is, it becomes basically the same function resulting from the Overload when we use function (considering, of course, the differences between function and Arrow Function).

But I’d still prefer to use the first option (function), or the suggestions of another answer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.