{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12041","repository_url":"https://api.github.com/repos/Microsoft/TypeScript","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12041/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12041/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12041/events","html_url":"https://github.com/Microsoft/TypeScript/issues/12041","id":187234481,"number":12041,"title":"Suggestion: A better and more 'classic' function overloads for TypeScript","user":{"login":"Shlomibo","id":5504779,"avatar_url":"https://avatars.githubusercontent.com/u/5504779?v=3","gravatar_id":"","url":"https://api.github.com/users/Shlomibo","html_url":"https://github.com/Shlomibo","followers_url":"https://api.github.com/users/Shlomibo/followers","following_url":"https://api.github.com/users/Shlomibo/following{/other_user}","gists_url":"https://api.github.com/users/Shlomibo/gists{/gist_id}","starred_url":"https://api.github.com/users/Shlomibo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shlomibo/subscriptions","organizations_url":"https://api.github.com/users/Shlomibo/orgs","repos_url":"https://api.github.com/users/Shlomibo/repos","events_url":"https://api.github.com/users/Shlomibo/events{/privacy}","received_events_url":"https://api.github.com/users/Shlomibo/received_events","type":"User","site_admin":false},"labels":[{"id":113971226,"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Duplicate","name":"Duplicate","color":"f7c6c7","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":21,"created_at":"2016-11-04T01:41:27Z","updated_at":"2017-01-15T09:44:34Z","closed_at":null,"body":"<!-- SUGGESTIONS: See https://github.com/Microsoft/TypeScript-wiki/blob/master/Writing-Good-Design-Proposals.md -->\r\n**TypeScript Version:**  2.0.3 / nightly (2.1.0-dev.201xxxxx)\r\n\r\n\r\nToday's function overloading in TypeScript is a compromise which is the worst of both worlds of dynamic, non-overloaded language like JS, and static, overloaded language such as TS.\r\n\r\n**Static languages weakness:**\r\nIn a static language such as TypeScript or C#, most types must be provided with the code. This put some burden on the programmer.\r\n\r\n```C#\r\nclass Example\r\n{\r\n    //            ******          ***\r\n    public static string Overload(int num)\r\n    {\r\n        return num.ToString();\r\n    }\r\n    //            ******          ******      ***\r\n    public static string Overload(string str, int num)\r\n    {\r\n        return str + num.ToString();\r\n    }\r\n}\r\n```\r\n\r\n**JS weakness:**\r\nIn JS, there are no function overloads as they cannot be inferred from each other. All parameters are optional and untyped.\r\nBecause of that the programmer has to provider the logic for inferring which functionality is desired, and execute it. If the code should be divided into more specific functions, they must be named differently.\r\n\r\n```js\r\nfunction overload(numOrStr, optionalNum) {\r\n\treturn typeof numOrStr === 'number'\r\n\t\t? implementation(numOrStr)\r\n\t\t: otherImplementation(numOrStr, num);\r\n}\r\nfunction implementation(num) {\r\n\treturn num.toString();\r\n}\r\nfunction otherImplementation(str, num) {\r\n\treturn str + num;\r\n}\r\n```\r\nNothing in the code would suggest to a future programmer that the last two functions are related.\r\n\r\n**TypeScript has both problems, and more...**\r\nBecause TS is bound to JS, only one overloaded function can have a body, and that function must has parameters that are compatible with all other overloads.\r\nThat function must resolve the desired behavior by the the parameters` types and count.\r\n\r\n```ts\r\n                       ******   ******\r\nfunction overload(num: number): string;\r\n                       ******       ******   ******\r\nfunction overload(str: string, num: number): string;\r\n                            ***************        ******   ******\r\nfunction overload(numOrStr: number | string, num?: number): string {\r\n    return typeof numOrStr === 'number'\r\n    ? implementation(numOrStr)\r\n                                    *   **********                   *\r\n    : otherImplementation(numOrStr, (num as number /* are we sure? */));\r\n}\r\n                             ******\r\nfunction implementation(num: number) {\r\n    // We must validate our input type.\r\n    if (typeof num !== 'number') {\r\n        throw new Error();\r\n    }\r\n    return num.toString();\r\n}\r\n                                  ******       ******\r\nfunction otherImplementation(str: string, num: number) {\r\n    // !! Must validate the types of input parms!\r\n    return str + num;\r\n}\r\n```\r\nLook how much longer it is than both previous examples, but with providing little benefits to code readability and maintenance, if any.\r\nWe could shorten it a bit by disabling type checking in the real overload, but then we loose type checking for the compatibility of the overloads.\r\n\r\nEither way, there's no checking that the programmer *actually* handles all the declared overloads.\r\n\r\n```ts\r\n                       ******   ******\r\nfunction overload(num: number): string;\r\n                       ******       ******   ******\r\nfunction overload(str: string, num: number): string;\r\nfunction overload(numOrStr, num) {\r\n    return typeof numOrStr === 'number'\r\n    ? implementation(numOrStr)\r\n    : otherImplementation(numOrStr, num);\r\n}\r\n                             ******\r\nfunction implementation(num: number) {\r\n    // We must validate our input type.\r\n    if (typeof num !== 'number') {\r\n        throw new Error();\r\n    }\r\n    return num.toString();\r\n}\r\n                                  ******       ******\r\nfunction otherImplementation(str: string, num: number) {\r\n    // !! Must validate the types of input parms!\r\n    return str + num;\r\n}\r\n```\r\n\r\n### **My suggestion:** at least lets have validation that overloads are handled, and enjoy the better semantics of the static languages.\r\n**Syntax:**\r\nLike current TS overloads, all overloads must be specified one after another. No other code can separate them.\r\nUnlike current TS overloads, all overloads must have a body.\r\n\r\n```ts\r\n// OK, current syntax\r\nfunction overload(num: number): string;\r\nfunction overload(str: string, num: number): string;\r\nfunction overload(numOrStr, num) {\r\n    // code\r\n}\r\n-------------------------------------------------------------\r\n// OK, new syntax\r\nfunction overload(num: number): string {\r\n    // code\r\n}\r\nfunction overload(str: string, num: number): string {\r\n    // code\r\n}\r\nfunction overload(numOrStr, num) {\r\n    // code\r\n}\r\n------------------------------------------------------------\r\n// ERROR. No mix and match\r\nfunction overload(num: number): string; // Error: missing body\r\nfunction overload(str: string, num: number): string {\r\n    // code\r\n}\r\nfunction overload(numOrStr, num) {\r\n    // code\r\n}\r\n\r\nfunction overload(num: number): string {\r\n    // code\r\n}\r\nfunction overload(str: string, num: number): string; // Error: missing body\r\nfunction overload(numOrStr, num) {\r\n    // code\r\n}\r\n---------------------------------------------------------------\r\n// ERROR: No code between overloads\r\nfunction overload(num: number): string {\r\n    // code\r\n}\r\nconst iMNotSupposeToBeHere = 'xxx';\r\nfunction overload(str: string, num: number): string { // Error: duplicate function implementation\r\n    // code\r\n}\r\nfunction overload(numOrStr, num) {\r\n    // code\r\n}\r\n```\r\n\r\nFor readability purpose, I would suggest the first overload would be the *entry-overload*. That is the function that is exposed to JS code, but is hidden from TS code.\r\nThat function would infer the desired behavior, and call the appropriate overload\r\n\r\n```ts\r\nfunction overload(numOrStr: number | string, num?: number): string {\r\n    return typeof numOrStr === 'number'\r\n        ? overload(numOrStr)\r\n        : overload(numOrStr, (num as number));\r\n}\r\nfunction overload(num: number): string {\r\n    // We must validate our input type.\r\n    if (typeof num !== 'number') {\r\n        throw new Error();\r\n    }\r\n    return num.toString();\r\n}\r\nfunction overload(str: string, num: number): string{\r\n    // !! Must validate the types of input parms!\r\n    return str + num;\r\n}\r\n```\r\nAn overload must be called from reachable code in the *entry-overload*:\r\n\r\n```ts\r\nfunction overload(numOrStr, num) {\r\n    return overload(numOrStr, num);\r\n}\r\nfunction overload(num: number): string { // ERROR: overload is not referenced from entry function.\r\n    // We must validate our input type.\r\n    if (typeof num !== 'number') {\r\n        throw new Error();\r\n    }\r\n    return num.toString();\r\n}\r\nfunction overload(str: string, num: number): string{\r\n    // !! Must validate the types of input parms!\r\n    return str + num;\r\n}\r\n```\r\nIn this syntax, the programmer has to provide an implementation that handles a declared overload, and each overload handle its parameters, validates them, and define the desired behavior.\r\nThe code implies that all these functions are related, and enforce that they would not be separated.\r\n\r\nAll other semantics and syntax regarding overload resolution, type checking, generics, etc' should remain the same as it is.\r\n\r\n**Emitted code:** \r\nThe overloaded implementations should be moved into the scope of the *entry-overload*. A bit of name mangling is required because JS cannot support overloads\r\nFrom the previous example:\r\n```ts\r\nfunction overload(numOrStr: number | string, num?: number): string {\r\n    return typeof numOrStr === 'number'\r\n        ? overload(numOrStr)\r\n        : overload(numOrStr, (num as number));\r\n}\r\nfunction overload(num: number): string {\r\n    // We must validate our input type.\r\n    if (typeof num !== 'number') {\r\n        throw new Error();\r\n    }\r\n    return num.toString();\r\n}\r\nfunction overload(str: string, num: number): string{\r\n    // !! Must validate the types of input parms!\r\n    return str + num;\r\n}\r\n```\r\nWe can very simply generate this code:\r\n```js\r\nfunction overload(numOrStr, num) {\r\n    return typeof numOrStr === 'number'\r\n        ? overload$number(numOrStr)\r\n        : overload$string$number(numOrStr, num);\r\n// }  <- Oh, no\r\n    // Well, it is unfortunate\r\n    function overload$number(num) {\r\n        // We must validate our input type.\r\n        if (typeof num !== 'number') {\r\n            throw new Error();\r\n        }\r\n        return num.toString();\r\n    }\r\n    function overload$string$number(str, num){\r\n        // !! Must validate the types of input parms!\r\n        return str + num;\r\n    }\r\n} // Ow, here I am\r\n```\r\n\r\nNotice that the output stays coherent. \r\nBecause the parameters are already in the scope of the implementations overloads, and if the respective parameter names are the same, or if renaming them would not introduce a naming conflict; we can omit the parameters and arguments from the implementation overloads.\r\n\r\n```js\r\nfunction overload(numOrStr, num) {\r\n    return typeof numOrStr === 'number'\r\n        ? overload$number()\r\n        : overload$string$number();\r\n\r\n    // Well, it is unfortunate\r\n    function overload$number() {\r\n        // We must validate our input type.\r\n        if (typeof num !== 'number') {\r\n            throw new Error();\r\n        }\r\n        return num.toString();\r\n    }\r\n    function overload$string$number(){\r\n        // !! Must validate the types of input parms!\r\n        return str + num;\r\n    }\r\n}\r\n```\r\n\r\nWe must also check if the entry-overload hides an otherwise captured variable by closure of the implementations. in this case, the hiding variable should be renamed.\r\n```ts\r\nconst str = 'abc';\r\n\r\nfunction overload(str, num) {\r\n\r\n    return typeof str === 'number'\r\n        ? overload(num)\r\n        : overload(str, num);\r\n}\r\nfunction overload(num: number): string {\r\n    // We must validate our input type.\r\n    if (typeof num !== 'number') {\r\n        throw new Error();\r\n    }\r\n    return str + num;\r\n}\r\nfunction overload(str: string, num: number): string{\r\n    // !! Must validate the types of input parms!\r\n}\r\n```\r\nShould transpiled to something like \r\n\r\n```js\r\nconst str = 'abc';\r\n\r\nfunction overload(_str, num) {\r\n\r\n    return typeof str === 'number'\r\n        ? overload$number(num)\r\n        : overload$string$number(_str, num);\r\n\r\n    function overload$number(num) {\r\n        // We must validate our input type.\r\n        if (typeof num !== 'number') {\r\n            throw new Error();\r\n        }\r\n        return str + num;\r\n    }\r\n    function overload$string$number(str, num) {\r\n        // !! Must validate the types of input parms!\r\n    }\r\n}\r\n```\r\n\r\nBut I don't think this would be a common case.\r\n\r\nI really really hope you would consider my suggestion. "},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/272684443","html_url":"https://github.com/Microsoft/TypeScript/issues/12041#issuecomment-272684443","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12041","id":272684443,"user":{"login":"Shlomibo","id":5504779,"avatar_url":"https://avatars.githubusercontent.com/u/5504779?v=3","gravatar_id":"","url":"https://api.github.com/users/Shlomibo","html_url":"https://github.com/Shlomibo","followers_url":"https://api.github.com/users/Shlomibo/followers","following_url":"https://api.github.com/users/Shlomibo/following{/other_user}","gists_url":"https://api.github.com/users/Shlomibo/gists{/gist_id}","starred_url":"https://api.github.com/users/Shlomibo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shlomibo/subscriptions","organizations_url":"https://api.github.com/users/Shlomibo/orgs","repos_url":"https://api.github.com/users/Shlomibo/repos","events_url":"https://api.github.com/users/Shlomibo/events{/privacy}","received_events_url":"https://api.github.com/users/Shlomibo/received_events","type":"User","site_admin":false},"created_at":"2017-01-15T09:44:34Z","updated_at":"2017-01-15T09:44:34Z","body":"@RyanCavanaugh \r\nI believe this suggestion can solve the problems pointed out in these issues and few more\r\nhttps://github.com/Microsoft/TypeScript/issues/13235\r\nhttps://github.com/Microsoft/TypeScript/issues/13225\r\nWhile remaining faithful to the goals of TS.\r\nWould you consider looking at it? \r\n\r\nThank you very much"}}
{"actor":{"display_login":"Shlomibo"}}