Repository navigation
Suggestion: Compile time function overloading #3442
Description
Activity
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptIn DiscussionNot yet reached consensusNot yet reached consensus
on Jun 9, 2015 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.
zpdDG4gta8XKpMCd commented
on Jun 11, 2015 AuthorMore actionsJason 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
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!
Reacted by Alex ChugaevWhy 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.
zpdDG4gta8XKpMCd commented
on Jun 11, 2015 AuthorMore actionsJason 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
Reacted by pdoustiThe 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?
zpdDG4gta8XKpMCd commented
on Jun 11, 2015 AuthorMore actionsincreases 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?
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.
zpdDG4gta8XKpMCd commented
on Jun 12, 2015 AuthorMore actionsnaming 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
- addedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.and removedIn DiscussionNot yet reached consensusNot yet reached consensus
on Jun 12, 2015 RyanCavanaugh commented
on Jun 12, 2015 MemberMore actionsCan 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);
zpdDG4gta8XKpMCd commented
on Jun 12, 2015 AuthorMore actionsNo 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
Reacted by Michał Lytek13 remaining items
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.
Ezekiel Victor (@evictor) if it is unambiguous, what would you propose as an emit?
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.
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()"; } }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.
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.
Reacted by Landon Poch, Matt Lo and Jan Melcher- addedOut of ScopeThis idea sits outside of the TypeScript language design constraintsThis idea sits outside of the TypeScript language design constraintsand removedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Jun 4, 2016 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:
#12041Why 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?
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.
You know, the use of
get propertyandset propertyshow that this method overloading enhancement can be done. Not terribly difficult an enhancement for TypeScript language.You know, the use of
get propertyandset propertyshow 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.
- locked and limited conversation to collaborators
on Jul 31, 2018
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:
Example: