Skip to content

Suggestion: Compile time function overloading #3442

Description

As we know JavaScript doesn't support overloaded functions, it's when a few functions with different signatures share the same name within the same scope where they are defined. Overloading can still be achieved at runtime by checking arguments and dispatching the execution to a proper path.

TypeScript seems capable of doing static function overloading which can be resolved at the compile time. Here is one way of how it can be done:

  • a notion of a nominal and effective name of a function is required
  • a nominal name is an identifier which is used to name a function and reference it in the TypeScript code
  • an effective name is an identifier which is used to name a function and reference it in the JavaScript code
  • an effective name is optional, if omitted then the effective name should be the same as the nominal name
  • a nominal name is a subject for overloading and hence can be shared across more than one function within the same scope
  • an effective name has to be unique withing the scope
  • the developer is in charge for specifying both nominal and effective names
  • the TypeScript emitter should only use the effective names for generating the JavaScript code
  • a resolution of a nominal name to an effective name should be based on the signature information
  • the hypothetical syntax (TBD) for a function declaration utilizing both the effective and nominal name might look like the following:
function nominalName:effectiveName() : void {
}

Example:

// typescript

function format:formatNumber(value: number) : string {
     return number.toFixed(2);
}

funciton format:formatDate(value: Date): string {
    return date.toIsoString();
}

var when = format(new Date());
var howMuch = format(999.99);
/// generated javascript

function formatNumber(value: number) : string {
     return number.toFixed(2);
}

funciton formatDate(value: Date): string {
    return date.toIsoString();
}

var when = formatNumber(new Date());
var howMuch = formatDate(999.99);

Activity

  1. jhchen commented on Jun 10, 2015

    @jhchen

    Perhaps Typescript should just pick the effective name? A human would likely pick something similar anyways and part of the point of overloading is not having to think of a different name.

    Given your example:

    // typescript
    
    function format(value: number): string {
         return number.toFixed(2);
    }
    
    function format(value: Date): string {
        return date.toIsoString();
    }
    
    var when = format(new Date());
    var howMuch = format(999.99);

    Typescript could keep track of unique function signatures and append an index in the generated Javascript:

    function format1(value) {
         return value.toFixed(2);
    }
    
    function format2(value) {
        return value.toIsoString();
    }
    
    var when = format1(new Date());
    var howMuch = format2(999.99);

    Or it could pick more descriptive names by appending the type instead of the index:

    function formatNumber(value) {
         return value.toFixed(2);
    }
    
    function formatDate(value) {
        return value.toIsoString();
    }
    
    var when = formatDate(new Date());
    var howMuch = formatNumber(999.99);

    A downside of option 2 is the names could be very long for some functions.

  2. zpdDG4gta8XKpMCd commented on Jun 11, 2015

    @zpdDG4gta8XKpMCd
    Author

    Jason Chen (@jhchen), although you are right that the compiler is able to generate synthetic effective names, i don't think it will contribute to the readability of the generated code, saving a little hassle with giving nams isn't worth less readable code

  3. wgebczyk commented on Jun 11, 2015

    @wgebczyk

    If TS wants to be only transition to ES6+, then let's do what ES6+ spec says in that area.
    If TS wants to be somethng more than that, this is very helpful as many other features that are available to the moment of generating JS.

    Yes! I want language that has more features that JS(whatever), so please add as many as possible options that helps develop applications!

  4. jhchen commented on Jun 11, 2015

    @jhchen

    Why have overloading at all if you still have to pick another name? The whole point of overloading is addressing the problem that the name the programmer wants is already being used. Requiring an effective and nominal name not only does not solve this problem, it introduces new syntax and unintuitive syntax purely for the benefit of generated code (which people read less and less given source maps). Also the generated names I suggested is very readable, especially considering generated code is most read during debugging and the prevalence of anonymous functions in Javascript. Sure it's less readable than a handpicked name by a human but this is precisely the burden that overloading is supposed to free us from.

    In short, source code readability and developer productivity should be prioritized over generated code readability.

  5. zpdDG4gta8XKpMCd commented on Jun 11, 2015

    @zpdDG4gta8XKpMCd
    Author

    Jason Chen (@jhchen) inconvenience of picking an extra name is a one-time thing whereas being able to address different functions by the same name is a repeatable positive experience

    requiring an extra name does solve the problem

    it's one of the primary goals of typescript to keep generated javascript readable

    agree, the syntax might or might not be intuitive, subject for discussion

    generated names that you have suggested do not account for multiple arguments whose types might be extra awkward and ugly

    in short

    • the developer has to be in charge for giving names
    • inconvenience is mild and one time thing
    • primary goals of typescript are met

    to kill both birds:

    • both nominal and effective name can be specified
    • effective name can be optional
    • if a nominal name is reused but effective name is omitted then a synthetic effective name is generated
  6. kitsonk commented on Jun 11, 2015

    @kitsonk
    Contributor

    The thing that strikes me about this is, what problem are you trying to solve?

    The disadvantages of perpetuating this "overloading" into JavaScript is that it increases the emitted code footprint dramatically and emits items that are very hard to optimise out in a subsequent build step in another tool chain. Why are you trying to place this unnecessary overhead on every other user of TypeScript so you can just perpetuate 100% of the language features of TypeScript into JavaScript?

  7. zpdDG4gta8XKpMCd commented on Jun 11, 2015

    @zpdDG4gta8XKpMCd
    Author

    increases the emitted code footprint dramatically

    can't see how naming without overloading that i originally suggested (with handpicked effective name) is better as far as the size of footprint?

    emits items that are very hard to optimise out in a subsequent build step in another tool chain

    can you elaborate on this?

  8. jhchen commented on Jun 11, 2015

    @jhchen

    inconvenience is mild and a one time thing

    This is probably the source of disagreement. In my experience I’ve found it to be true that naming is one of the few hard problems in computer science.

    the syntax might or might not be intuitive, subject for discussion

    Agree this is subjective. In the current form it resembles namespace syntax in other languages but agree the specifics could be discussed. However, I’m not aware of any other language has a syntax specification purely for the benefit of generated code, and since intuition is informed by experience, in my opinion the idea of a nominal+effective name itself is also unintuitive.

    primary goals of typescript are met (readability of the generated code)

    This should be weighed against other primary goals like developer productivity and source code maintainability.

    effective name can be optional

    This may be the right compromise but it’s worth noting this path is still not free. There’s still the implementation effort for the Typescript team, the subsequent documentation and support, and the cognitive overhead for the developers that read the manual in full.

    But if this is the desirable route then I think this can be two separate Github Issues? One for function overloading supported by compiler generated names, and another Issue for the optional nominal+effective syntax to control the generated names.

  9. zpdDG4gta8XKpMCd commented on Jun 12, 2015

    @zpdDG4gta8XKpMCd
    Author

    naming is one of the few hard problems in computer science

    agree, good and consistent naming is a hard thing to come by (that's why we are here talking about this feature), however not-as-good yet meaningful names aren't too hard to make up, and this is what handpicked effective names are, just good enough for the job to get done

    since intuition is informed by experience

    i am glad we pay so much attention to very personal subjective things, let's ask your experience one more time, how many languages do you know that proclaim readable generated code as one of their goals?

    this path is still not free

    i am with you on that, didn't you just say that naming is one of the hardest problems? all right.. so do you truly believe that using names provided by the developer is more work than developing a heuristic algorithms for making good looking names automatically?

    by saying so, i conclude, you must have already developed one for all crazy signatures you can see in the wild:

    function format(value: { value: typeof value }): string { return undefined; }
    
    function format(value: Either<Promised<Optional<Workload>>, [{ value: string }, { value: number }, Promised<boolean[]>]>): string { return undefined; }

    if so you should not hesitate and include it as a part of the formal proposal

  10. added
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    and removed on Jun 12, 2015
  11. RyanCavanaugh commented on Jun 12, 2015

    @RyanCavanaugh
    Member

    Can you fill in the emit for this block?

    var x = someCondition ? format : someOtherFunction;
    x("");
    x(32);
    
    function fn(x: any) { /* some body */ }
    fn(format);
    
    var y: any = someCondition ? "" : 32;
    format(y);
  12. zpdDG4gta8XKpMCd commented on Jun 12, 2015

    @zpdDG4gta8XKpMCd
    Author

    No problem:

    // if there is a common (comparible?) signature between someOtherFunction and one of the overloads
    // then x is of the type of such signature, otherwise we get an unresolved type error
    var x = someCondition ? format : someOtherFunction; 
    x("");
    x(32);
    
    function fn(x: any) { /* some body */ }
    fn(format); // <-- type error, resolving from nominal to effective is based on the signature which is erased here
    
    var y: any = someCondition ? "" : 32;
    format(y); // <-- if there is an overload for `string|number` no problem, otherwise a type error
    /// UPDATE: didn't see `any`, if there is an overload for `any` then we use it, otherwise a type error 
  13. 13 remaining items

  14. evictor commented on Feb 26, 2016

    @evictor

    Kitson Kelly (@kitsonk) The code path is entirely unambiguous; the param types are part of a function's signature and serve to disambiguate resolution. I.e. a call to alertSomething(x: number) results in a call to alertSomething(x: string) which in its body does alert(x: string).

    In your example, the answer is it's a compile time error—unable to resolve which overload to use. This is perfectly reasonable and is the behavior in many languages which allow this practice. If you were so inclined, then you could disambiguate your call with the foobar object by typing it in an unambiguous way, such as assigning it to a var explicitly typed as a Foo, or an Object, or what have you.

  15. kitsonk commented on Feb 26, 2016

    @kitsonk
    Contributor

    Ezekiel Victor (@evictor) if it is unambiguous, what would you propose as an emit?

  16. evictor commented on Feb 27, 2016

    @evictor

    It would be difficult to approach renaming the overloading functions as someone else in this thread suggested because external things could be relying on the one name. I think the best way to do it would be to write out exactly what a human has to write now in order to do this runtime "pseudo-overloading" and keep the original method name so no external references to the symbol need to be changed, e.g.:

    // Theoretical TypeScript as before
    function alertSomething(str: string): void {
      alert(str);
    }
    
    function alertSomething(num: number): void {
      alertSomething(num.toString());
    }
    
    // Emitted JavaScript
    function alertSomething() {
      if(typeof arguments[0] == 'string')
        alert(arguments[0]);
      else if(typeof arguments[0] == 'number')
        alertSomething(arguments[0].toString());
      else
        throw 'TypeScript runtime error; unexpected arguments to alertSomething()';
    }
    

    Then of course this could be extended so that number of arguments could be considered, etc. This is a viable solution because you can compile that down right there into the resultant JavaScript without having knowledge of external callers using whichever overloads (since the symbol name ultimately stays the same), and ultimately the end user (programmer) would end up writing exactly that sort of runtime type checking anyway.

  17. ToastHawaii commented on Feb 27, 2016

    @ToastHawaii

    This is dangeres if you have varibales mit the same name in the function.
    I suggest:

    function alertSomething() {
    
        const _overload1 = function(num) {
            alertSomething(num.toString());
        };
        const _overload2 = function(str) {
            alert(str);
        };
    
        if (typeof arguments[0] === "number") {
            return _overload1.apply(this, arguments);
        } else {
            // I think every call sould end in a overload
            return _overload2.apply(this, arguments);
        }
    }
    

    Or

    function alertSomething() {
    
        const _overload1 = function(str) {
            alert(str);
        };
        const _overload2 = function(num) {
            alertSomething(num.toString());
        };
    
        if (typeof arguments[0] === "string") {
            return _overload1.apply(this, arguments);
        } else if (typeof arguments[0] === "number"){
            return _overload2.apply(this, arguments);
        } else {
            throw "TypeScript runtime error; unexpected arguments to alertSomething()";
        }
    }
    
  18. Chris2011 commented on May 30, 2016

    @Chris2011

    We had some discussion in the slack chat of https://angularjs-de.slack.com (Typescript chan) I used GWT for a long time and I wondered by myself, why is real overloading in TS not possible, but in GWT. Both generated JS code. So someone came up with an idea, how GWT could do this (Only an idea)

    Java (Used in GWT)

    public String foo(String a) { ... }
    public String foo(int a) { ... }

    JavaScript (Output)

    function foo_string(a) { ... }
    function foo_int(a) { ... }

    Using:

    Java (Used in GWT)

    String bar0 = foo("bar");
    String bar1 = foo(1);

    JavaScript (Output)

    var bar0 = foo_string("bar");
    var bar1 = foo_int(1);

    And this is similar to this comment: #3442 (comment)

    So it would be very handy, if we can have this in TypeScript too. I heard about the philosophy to not generate stuff which is not supported in JS at all, but if you generate the code, concate the files, minify the files (e.g. for a spa) than you don't want to read the code which was generated anymore. You only write in TS.

  19. mhegazy commented on Jun 4, 2016

    @mhegazy
    Contributor

    This proposal violates multiple of the TypeScript design goals (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals); specifically. breaking changes (11), changing behavior of a JS program (7), and, though a subjective argument, generates not-pretty-code (4).

    The other issue is it relies on type-directed emit. something that would break single file transpilation scenarios.

    for all of these, this proposal is out of scope for the TypeScript project for the time being.

  20. added
    Out of ScopeThis idea sits outside of the TypeScript language design constraints
    and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Jun 4, 2016
  21. Shlomibo commented on Jan 13, 2017

    @Shlomibo

    Why require the dispatch to be static? It would not fit into JS world.
    We also don't have to generate the code for dynamic dispatch. The programmer already doing it.

    Yet, IMHO we can still have better type-checking and validations over overloaded functions:
    #12041

  22. G1itcher commented on Nov 25, 2017

    @G1itcher

    Why can't function overloading simply be implemented as syntactic sugar for the regular single function with the standard pattern of typeof/instanceof checking?

    i.e.

    function foo(x: number){/*body Foo 1*/}
    function foo(x: string, y: number){/*body Foo 2*/}
    

    emits to

    function foo() {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        if (args.length === 1 &&
            typeof args[0] === "number") {
            /*body Foo 1*/
        }
        else if (args.length === 2 &&
            typeof args[0] === "string" &&
            typeof args[1] === "number") {
            /*body Foo 2*/
        }
    }
    

    obviously, the emit isn't beautiful, but it's certainly readable and is currently what is emitted for function(...args) anyway. An alternative would be to boil down an overloaded function into the equivalent Typescript function defined with optionals and unions

    i.e.

    function foo(x: number){/*body Foo 1*/}
    function foo(x: string, y: number){/*body Foo 2*/}
    

    would be equivalent to

    function foo(x: number | string, y?: number) {
        if (typeof x === "number" && y === null) {
            /* Foo body 1*/
        }
        else if (typeof x === "string" && y != null) {
            /* Foo body 2*/
        }
    }
    

    which emits to

    
    function foo(x, y) {
        if (typeof x === "number" && y === null) {
            /* Foo body 1*/
        }
        else if (typeof x === "string" && y != null) {
            /* Foo body 2*/
        }
    }
    

    Am I oversimplifying this issue? Obviously making Typescript enforce typings and usage of the overloaded functions in IDE and compile time is a different problem, but with my limited knowledge, it seems like a half solved problem?

  23. kitsonk commented on Nov 26, 2017

    @kitsonk
    Contributor

    G1itcher that would work for a very limited use cases. What about something like this?

    function foo(x: string[]): void;
    function foo(x: { [key: string]: string }): void;

    Ultimately, your scenario would only work when primitive values are used as arguments. Also, you suggestion doesn't cater for overloading of return types.

  24. fletchsod-developer commented on Nov 27, 2017

    @fletchsod-developer

    You know, the use of get property and set property show that this method overloading enhancement can be done. Not terribly difficult an enhancement for TypeScript language.

  25. kitsonk commented on Nov 27, 2017

    @kitsonk
    Contributor

    You know, the use of get property and set property show that this method overloading enhancement can be done.

    You know, that is valid JavaScript syntax, versus some special TypeScript construct. That isn't function overloading, that is interpreting JavaScript syntax.

    As Mohamed stated above it isn't the technical difficulty, it is that it break multiple design goals and non-goals of TypeScript.

  26. locked and limited conversation to collaborators on Jul 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions