-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Compile time function overloading #3442
Comments
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. |
@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. Yes! I want language that has more features that JS(whatever), so please add as many as possible options that helps develop applications! |
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. |
@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
to kill both birds:
|
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? |
can't see how naming without overloading that i originally suggested (with handpicked effective name) is better as far as the size of footprint?
can you elaborate on this? |
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.
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.
This should be weighed against other primary goals like developer productivity and source code maintainability.
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. |
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
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?
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 |
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); |
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 |
That isn't an emit... that is copying TypeScript and adding some comments.
Every "overload" will emit a full function. Current implementation means that in a lot of use cases dealing with permutations of arguments can be handled in one line of code. Why put the overhead of a full function on the the developer in TypeScript and in the emitted code? Your proposal does not preserve current functionality, so I would have no choice of having to use your suboptimal solution to utilise overloading in TypeScript. No thank you...
Most code optimisation in JavaScript works off tree pruning, where code branches that are unaccessible are trimmed (on top of other minimisation functions). It is typical that only advanced compiler functions would optimise out unused functions, and even then, if I have a library that I am planning on using, the final code would likely not have optimised out any of these functions. So again, if I can resolve the permutations of the overloaded argument with a single line of code, I still have to bear the burden of all these emitted functions, which increase the memory foot print of the code. I could hope and pray that the JIT is smarter. You still didn't answer my question about what problem you are trying to solve... I also find it hard to see how this could even be considered an improvement over the current implementation. |
if you look closely there is nothing to emit either because of an obviously unresolvable situation or lack of details, i believe all these corner cases were picked to illustrate possible difficulties, a typical situation (when a signature is clear and known) is as easy as pie
it looks like you are not getting the idea, there is no overhead and nothing that breaks how typescript currently works as long as you avoid using this feature, no breaking changes at all
indeed currently available overloading (resolved at runtime) do require examining aruments which is an optimization killer, since no assumptions can be made as for what agruments array is every single time the function is called: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments what i am suggesting is 180 degrees opposite, let's not do runtime overloading if it can be done statically at the compile time and then optimized no problem at runtime, how hard is that? this feature is a good example of a zero runtime cost abstraction which is achieved 100% at the compiler's expense
unused function should be removed at the compile time since all information is available for doing static analysis, again this feature has nothing to do with a number of unused functions that would have been there anyway just named differently (uniquely)
um... didn't you happen to hear about overloading at all? here are a few links to start with:
a problem being solved:
|
Keep it unsarcastic, please. |
Not if you are exporting the functions.
I understand the concept of overloading, which TypeScript supports. What I am unclear on is what problem are you trying to solve that the current implementation doesn't cover. You seem to indicate in your further comments that it is "allowing a user to address different functions by the same name" in the emitted JavaScript. If you are trying to solve that, I am not sure why. You also indicate that "indeed currently available overloading (resolved at runtime) do require examining aruments which is an optimization killer" though I am having a difficult time conceiving where your suggested approach would on average be more optimised. Are there some examples of the current implementation and your suggested implementation where you can highlight how the suggestion would improve optimisation? (If any of my comments are being perceived as sarcastic, I apologise, not my intent) |
Even if you are exporting them, given that you have the entire code base at hands, you can see what's used and what is not. For libraries it's a different story.
Current TypeScript implementation allows function overloading that has to be resolved at runtime by examining arguments. My idea is to leverage the type system and do overload resolution at the compile time basing it on function signatures. This way we have 0 performance penalty at runtime and convenience of using the overloaded functions in TypeScript that will be resolved, renamed, and emmitted as uniquely named functions in JavaScript - all by the compiler without having to do anything at runtime. Effectively it means that by looking at the generated JavaScript code you won't be able to tell whether a given function was overloaded or it was not in the source code in TypeScript. No trace whatsoever. This is what I mean by a zero cost abstraction 100% done by the compiler. |
I think the implementation of "TypeScript should know what I want to call" is a bit problematic. Instead, TypeScript could check what can be type checked during runtime and automatically emit runtime-type-checked functions from overloads. Curent Situation interface User {
name: string;
}
function greet(message: string, userName: string); // Not emitted
function greet(message: string, user: User); // Not emitted
function greet(message: string, userOrUserName: any) { // Emitted without type info
let userName: string;
if (typeof userOrUserName === 'string') { // Manual checking
userName = <string>userOrUserName;
} else { // Can't check User, but it can be assumed
userName = (<User>userOrUserName).name;
}
console.log(`${message}, ${userName}`);
} Proposal interface User {
name: string;
}
function greet(message: string, userName: string) {
console.log(`${message}, ${userName}`);
}
function greet(message: string, user: User) {
greet(message, user.name);
} Emitting Process
Emitted Code function greet(message, userOrUserName) {
// Emit overloads
var overload_1 = function (message, userName) {
console.log(message + ', ' + userName);
};
var overload_2 = function (message, user) {
greet(message, user.name);
};
// Overloads we have are (message: string, userOrUserName: string) and (message: string, userOrUserName: User)
// No need to check "message" since it's "string" in all overloads
// Check "userOrUserName"
if (typeof userOrUserName === 'string') { // String can be checked in runtime
return overload_1(message, userOrUserName);
} else { // User can not be checked in runtime, but it's no problem since we can "else" it
return overload_2(message, userOrUserName);
}
} More Examples interface Friend {
name: string;
}
interface Pet {
name: string;
}
/* Error! No way to check Friend and Pet in runtime. */
function brag(friend: Friend) {
alert(`I have a cool friend named ${friend.name}.`);
}
function brag(pet: Pet) {
alert(`I have a cool pet named ${pet.name}.`);
}
/* No problem. Can easily be done with a "x === undefined" check. */
function bragFriend() {
alert(`I have cool friends.`);
}
function bragFriend(friend: Friend) {
alert(`I have a cool friend named ${friend.name}.`);
}
/* No problem. Can easily be done with a "typeof x === 'string'" check. */
function bragFriend_2(name: string) {
alert(`I have a cool friend named ${name}.`);
}
function bragFriend_2(friend: Friend) {
alert(`I have a cool friend named ${friend.name}.`);
} |
I am not suggesting that the current style of overloading should be dropped. This should work: interface Braggable {
name: string;
}
interface Friend extends Braggable {
lastName: string;
}
interface Pet extends Braggable {
breed: string;
}
interface Boat extends Braggable {
model: string;
}
function brag(name: string) { // Has to be "auto-runtime-checkable"
alert(`I have a cool thing named ${name}.`);
}
function brag(friend: Friend); // Just needs to be compatible with (braggable: Braggable)
function brag(pet: Pet); // Just needs to be compatible with (braggable: Braggable)
function brag(braggable: Braggable) { // Has to be "auto-runtime-checkable"
// Manual checks have to be done
if (braggable.lastName) {
alert(`I have a cool friend named ${braggable.name}.`);
} else if (braggable.breed) {
alert(`I have a cool pet named ${braggable.name}.`);
}
} |
I love the suggestion from AlicanC. This feature is very useful for the peoples from the C# and Java world and it makes the TypeScript code more readable. I have tried to think this through and I would like to share my results. Backward compatibleAs AlicanC wrote the feature is full backward compatible, so the following example would still work. function f();
function f(n: number);
function f(n?: number) {
if(n) {
alert("Argument n is " + n);
} else {
alert("None arguments.");
}
} Simple exampleBut it is difficult to read. A better way is to write it like this. function f() {
alert("None arguments.");
}
function f(n: number) {
alert("Argument n is " + n);
} this could compile to // The compiled function contains none arguments. Instate the arguments object is used.
function f() {
const _overload1 = function() {
alert("None arguments.");
};
const _overload2 = function(n) {
alert("Argument n is " + n);
}
// If a argument is undefined it means it is not set. The same as with optional parameters.
if(arguments[0] === void 0) {
// Every overload is wrapped with "return (...).apply(this, arguments);" this works for functions and class methods (and works as expected for "strict mode").
return _overload1.apply(this, arguments);
} else {
// Every call ends in a overlade call. Even if there is no exact match.
return _overload2.apply(this, arguments);
}
}
Process
List of automatically runtime checksThe first line is the argument expression. The second line is a suggestion for the automatically runtime check.
Basica: any
a !== void 0 // check if a argument exists
// or "a === void 0" for a argument does not exists.
PrimitivesFor primitives the typeof can be used. If an argument is null it means it is set. As I know, every type is compatible with null. a: boolean
a === null || typeof(a) === "boolean"
a: number
a === null || typeof(a) === "number"
a: string
a === null || typeof(a) === "string"
ClassesFor classes the instanceof can be used. a: Date
a === null || a instanceof Date ArraysArrays are classes so instanceof can be used. a: any[]
a === null || a instanceof Array FunctionsFor functions, the typeof can be used and additionally .length to check the number of expected arguments. a: () => any
a === null || typeof(a) === "function" && a.length <= 0
a: (b:any) => any
a === null || typeof(a) === "function" && a.length <= 1 Extended
Enumsa: Color // Color is an enum
a === null || typeof(a) === "number" && Color[a] !== void 0
// or if every number a valid enum value then
// a === null || typeof(a) === "number"
// but then there is none difference between the check for enum and number parameters... String literal typesa: "s"
a === null || a === "s" Typed arraysFor typed arrays the .every() method can be used to check the type from all elements. a: number[]
a === null || a instanceof Array && a.every(e => e === null || typeof(e) === "number")
Tublesa: [number, string]
a === null || a instanceof Array && a.length === 2 && (a[0] === null || typeof(a[0]) === "string") && (a[1] === null || typeof(a[1]) === "number") Interfaces// Interfaces are tricky. Is there really a good way to check automatically...?
// For simple interfaces that might work.
a: {x: number, y: number}
a === null || (a.x === null || typeof(a.x) === "number") && (a.y === null || typeof(a.y) === "number") Union typesa: number | Date
(a === null || typeof(a) === "number") || (a === null || a instanceof Date)
Optional and Default Parametersa?: number
(a === void 0) || (a === null || typeof(a) === "number")
a: number = 1
(a === void 0) || (a === null || typeof(a) === "number")
Rest Parameters...a: any[]
// Every state of arguments is valid for rest parameters.
...a: number[]
Array.prototype.slice.call(arguments, 0 /* Start index from the rest parameter */).every(e => e === null || typeof(e) === "number")
Genericsa: T // T is a generic type
// For generic type parameters at compile time the type is known, so the runtime check for this can be used.
a: C<T> // C is a class and T is a generic type.
a === null || a instanceof C
// For a generic class type parameters only the class can be checked. Complex examplefunction f() {
alert("none arguments");
}
function f(n: number) {
alert("n is " + n);
}
function f(s: string) {
alert("s is " + s);
}
function f(d: Date) {
alert("The iso from d is " + d.toISOString());
}
function f(a: any[]) {
alert("The length of a is " + a.length);
}
function f(b: boolean, s: string) {
alert("b is " + b + " and s is " + s);
}
function f(n: number, d: Date) {
alert("n is " + n + " and the iso from d is " + d.toISOString());
}
function f(n: number, i: {x:number, y:number}) {
alert("n is " + n + " and i.x is " + i.x + " and i.y is " + i.y);
} this could compile to function f() {
const _overload1 = function () {
alert("none arguments");
};
const _overload2 = function (n) {
alert("n is " + n);
};
const _overload3 = function (s) {
alert("s is " + s);
};
const _overload4 = function (d) {
alert("The iso from d is " + d.toISOString());
};
const _overload5 = function (a) {
alert("The length of a is " + a.length);
};
const _overload6 = function (b, s) {
alert("b is " + b + " and s is " + s);
};
const _overload7 = function (n, d) {
alert("n is " + n + " and the iso from d is " + d.toISOString());
};
const _overload8 = function (n, i) {
alert("n is " + n + " and i.x is " + i.x + " and i.y is " + i.y);
};
if (arguments[0] === void 0) {
return _overload1.apply(this, arguments);
} else if (arguments[1] === void 0) {
if (arguments[0] === null || typeof (arguments[0]) === "number") {
return _overload2.apply(this, arguments);
} else if (typeof (arguments[0]) === "string") {
return _overload3.apply(this, arguments);
} else if (arguments[0] instanceof Date) {
return _overload4.apply(this, arguments);
} else {
return _overload5.apply(this, arguments);
}
} else {
if (arguments[0] === null || typeof (arguments[0]) === "boolean") {
return _overload6.apply(this, arguments);
} else {
if (arguments[1] === null || arguments[1] instanceof Date) {
return _overload7.apply(this, arguments);
} else {
return _overload8.apply(this, arguments);
}
}
}
} Critical examplesFunctionfunction f(a: (a: number) => string) {
alert("a returns " + a(1));
}
/* Error! No safe way to automatically check at runtime. */
function f(a: (a: string) => string) {
alert("a returns " + a("a"));
} Interfaceinterface I1 {
p: string;
}
interface I2 {
p: string;
}
function f(i: I1) {
alert("i.p of i:I1 is " + i.p);
}
/* Error! No safe way to automatically check at runtime. */
function f(i: I2) {
alert("i.p of i:I2 is " + i.p);
} Union typefunction f(b: boolean) {
alert("b is " + b);
}
/* Error! No safe way to automatically check at runtime. */
function f(bs: boolean | string) {
alert("bs is " + bs);
} Optional and Default Parametersfunction f(b: boolean) {
alert("b is " + b);
}
/* Error! No safe way to automatically check at runtime. */
function f(b?: boolean = true) {
alert("b? is " + b);
} Enum and numberenum E {
v1,
v2
}
function f(e: E) {
alert("e is " + e);
}
function f(n: number) {
alert("n is " + n);
} this could compile to var E;
(function (E) {
E[E["v1"] = 0] = "v1";
E[E["v2"] = 1] = "v2";
})(E || (E = {}));
function f() {
const _overload1 = function(e) {
alert("e is " + e);
};
const _overload2 = function(n) {
alert("n is " + n);
};
if(arguments[0] === null || typeof(arguments[0]) === "number" && E[arguments[0]] !== void 0) {
return _overload1.apply(this, arguments);
} else {
return _overload2.apply(this, arguments);
}
} but is it save to compile this? Array and tuplefunction f(t: [number, number]) {
alert("The t[0] of t is " + t[0] + " and t[1] is " + t[1]);
}
function f(a: number[]) {
alert("The length of a is " + a.length);
} this could compile to function f() {
const _overload1 = function(t) {
alert("The t[0] of t is " + t[0] + " and t[1] is " + t[1]);
};
const _overload2 = function(a) {
alert("The length of a is " + a.length);
};
if(a === null || a instanceof Array && a.length === 2) {
return _overload1.apply(this, arguments);
} else {
return _overload2.apply(this, arguments);
}
} but is it save to compile this? Rest parametersfunction f(n: number) {
alert("n is " + n);
}
function f(s: string, n: number) {
alert("s is " + s + " n is " + n);
}
function f(...r: number[]) {
alert("The length of r is " + r.length);
} this could compile to function f() {
const _overload1 = function(n) {
alert("n is " + n);
};
const _overload2 = function(s, n) {
alert("s is " + s + " n is " + n);
};
const _overload3 = function(...r) {
alert("The length of r is " + r.length);
};
if (arguments[0] === void 0) {
return _overload3.apply(this, arguments);
} else if (arguments[1] === void 0) {
return _overload1.apply(this, arguments);
} else if (arguments[2] === void 0) {
if (arguments[0] === null || typeof (arguments[0]) === "number") {
return _overload3.apply(this, arguments);
} else {
return _overload2.apply(this, arguments);
}
} else {
return _overload3.apply(this, arguments);
}
} but is it save to compile this? SummeryI think it is a very useful feature for developers who do not work every day with Typescript or JavaScript. But I also understand that this approach is fuzzy and not perfect. This kind of overloading support is not the JavaScript way to do it. |
Here's a simple example of common overloading desires that TypeScript confounds: Desired code (arguably totally unambiguous what the intent here is):
Yet you cannot do this in TypeScript. What you must do is cause type information and thus the usefulness of the compiler to be lost in the body of the single overload implementation:
Honestly this is dead obvious to me and the one thing about TypeScript that really bothers me. |
It is dead obvious to you, but in your first example, what would you propose as the dead obvious emit? |
// Overload 1
function alertSomething__1(str) {
alert(str);
}
// Overload 2
function alertSomething__2(num) {
alertSomething(num.toString());
}
// Overload Wrapper
function alertSomething(__whatever) {
if (typeof __whatever === 'string') {
return alertSomething__1.call(this, __whatever);
} else if (typeof __whatever === 'number') {
return alertSomething__2.call(this, __whatever);
}
} This really doesn't sound so magical to me. Are we missing something? |
TypeScript Goals:
TypeScript Non-Goals:
But putting that aside: function alertSomething({ foo: 'bar' }); // what happens? And what would you emit here: interface Foo {
foo: string;
}
interface FooFunction {
(): Foo;
}
function alertSomething(obj: Foo): void {
alert(obj.foo);
}
function alertSomething(fn: FooFunction): void {
alert(fn().foo);
}
function alertSomething(obj: Object): void {
alert(JSON.stringify(obj));
}
function alertSomething(str: string): void {
alert(str);
}
function alertSomething(num: number): void {
alert(String(num));
}
alertSomething({ foo: 'foo', bar: 'bar' }); |
I think another point from the Design Goals is the key here:
This should be something that is done by a "preprocessor", maybe even related to #6508 |
@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. |
@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.:
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.
Or
|
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. |
Why require the dispatch to be static? It would not fit into JS world. Yet, IMHO we can still have better type-checking and validations over overloaded functions: |
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.
emits to
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.
would be equivalent to
which emits to
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 |
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. |
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:
The text was updated successfully, but these errors were encountered: