You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Highly suggested 👍
This would add methods to existing types without creating a new derived type or modifying the original type and it would definitely make sense in linq to typescript query operators
A couple of questions:
What would be the compiled js like? And how would be the runtime interoperability with the existing class?
Extension methods in C# are static methods with syntactic sugar for instance method call syntax with the first parameter specifying the type the method operates on. Which brings some benefits beside the instance call syntax:
Extension methods can be called directly as static method call
Extension classes are not newable
There could be different extension methods on the same type in the same namespace as long as each class containing extension methods has its own identifier
Having considered extension class syntax, is extension class actually a class or a module at runtime? How do you translate this in a body of an extension method at runtime? (Function.apply, Function.call maybe?) And how would extension methods be like on an interface which I believe is the most commonly use case for extension methods?
I think due to first-class functions in JS there is a problem with this approach:
extensionclassShape{// Syntax??getArea(){return/* ... */;}}varx=newShape();vary=newShape();console.log(x.getArea);// Function or undefined?x.getArea.foo="bar";console.log(y.getArea.foo);// "bar" or undefined?
If extension methods are to be implemented, there would probably have to be restirictions on accessing/changing their properties.
In my opinion, one of the common uses of extension methods is to make them work on interfaces, not just classes, so you could do something along the lines of
interfaceIPoint{x:number;y:number;}classPointimplementsIPoint{constructor(publicx,publicy){// ...}}varp1:Point=newPoint(0,50);varp2:IPoint={x:32,y:32};console.log(p1.getLength());// extension methodconsole.log(p2.getLength());// same extension method
If possible, I think C#-like extension methods would work best:
extensionfunctiongetLength(thisp:IPoint):number{returnMath.sqrt(p.x*p.x+p.y*p.y);}// another extension method with the same name but for different classextensionfunctiongetLength(thiss:string):number{returns.length;}console.log(getLength);// compiler errorconsole.log(p1.getLength);// Function or undefined or compiler error?console.log(p1.getLength.foo);// compiler errorp1.getLength.foo="bar";// compiler error
Compiling to:
// jsfunction_IPoint$getLength(){returnMath.sqrt(this.x*this.x+this.y*this.y);}function_string$getLength(){returnthis.length;}console.log(_IPoint$getLength.call(p1));// OKconsole.log(_IPoint$getLength.call(p2));// OK
Reacted by Benoit Robin, Craig Goldstone, Daniil Degtyarev, James O'Cull, Fran Hodl, Matthew Layton, Julian Gilyadov, Stronger, davidrot and Eugene Strizhok
I don't think extension methods should be implemented by adding a function to the prototype! For one thing extending the prototype of built in types, is considered dangerous, 3rd party libraries may also add a method with the same name to the prototype, causing the behavior to depend on which JS file was loaded last, or worse you might overwrite the existing built-in methods! And for another, there's no way to have different method overloads for the extension methods defined in different modules!
More importantly, prototype extending works only for concrete classes, in a sense, no interface or declared class extension methods!
Reacted by Anthony Rosequist, Vasiliy Kostin, kpietraszko and Eugene Strizhok
3rd party libraries may also add a method with the same name to the prototype, causing the behavior to depend on which JS file was loaded last, or worse you might overwrite the existing built-in methods
this would be caught by name collision in the lib's .d.ts file and your custom.d.ts file.
More importantly, prototype extending works only for concrete classes, in a sense, no interface or declared class extension methods
I don't expect it there to be any codegen for ambient declarations. PS: If you ambiently say something is a class TypeScript already assumes it has a prototype. e.g.
declareclassFoo{}console.log(Foo.prototype);// Compiles fine
So adding to the ambient prototype is consistent with what already exists.
Perhaps examples of your concerns (if still there) would help.
this would be caught by name collision in the lib's .d.ts file and your custom.d.ts file.
This is only true if there is .d.ts metadata for every 3rd party library (which is not) and only when .ts files are being compiled together (ie. in the same project). Besides there are lots of .js libraries as part of server-side environment which are being injected into dynamic pages at runtime (take asp.net server controls for example)
If you ambiently say something is a class TypeScript already assumes it has a prototype
Ambient class declaration does not have implementation, which is not the case for extension methods, unless extension methods for ambient class declarations are being erased in the compiled .js (which I believe is counterproductive to it's purpose), the program might break at runtime!
And how about interfaces? To extend an interface new methods has to be added to the interface, but changing the interface by adding new methods would break compatibility with existing consumers! Take IEnumerable<T> in C# for example, I cannot imagine how many classes are already implementing this interface! Without having extension methods on interfaces, there could be no LINQ as we know it!
I believe the real advantage of extension methods is being loosely coupled with the existing type, e.g.
Array.prototype.join = function (inner, outerKeySelector, innerKeySelector, resultSelector) {
};
The above example is supposed to add linq-like join operator for arrays, however having altered the prototype, we have lost the built-in join method. Having extension methods separated in a concrete different method actually benefits better method overloading.
There's also a big benefit with generic extension methods which is reducing the need for a common base class.
The following is an example of using generic extension method in C#:
There's a key decision point here on this feature, which is how the emitted code would work. I see two options based on what's been discussed so far
Example code I'll use
classSquare{/* ... */}extensionfunctiongetArea(this: Square){// First param must be 'this'returnthis.width*this.height;}varx=newSquare(10,10),y: any=x;console.log(x.getArea());console.log(y.getArea());
Add to .prototype
/* Regular emit for 'Square' here */Square.prototype.getArea=function(){returnthis.width*this.height;}varx=newSquare(10,10),y=x;console.log(x.getArea());console.log(y.getArea());// Succeeds
Pros
Works even if type information is missing (y)
Accurately models many existing libraries' behavior
Cons
Doesn't work for interfaces, which is probably a primary use case
Adding to prototype is often a bad idea, especially for built-in objects
Rewrite call sites
/* Regular emit for 'Square' here */__Square_getArea=function(){returnthis.width*this.height;}varx=newSquare(10,10),y=x;console.log(__Square_getArea.call(x));console.log(y.getArea());// Fails
Pros
Works on interfaces as well as classes
Don't mess around with prototype, so it's safe for built-in types
Cons
Unclear what it means to use this method in a non-call position
Fails unexpectedly when strong type information is absent
Doesn't model existing JS library behavior; we'd still then need an alternate syntax for those
Ryan Cavanaugh (@RyanCavanaugh) I would alter the example code, to use the type parameter for the extension method instead of dynamic binding of this :
classSquare{/* ... */}extensionfunctiongetArea(thissquare: Square){// First param must be 'this'returnsquare.width*square.height;}varx=newSquare(10,10);console.log(x.getArea());
So that the function behave like a static helper method and it's valid to call it non-instance way. However still fails when strong type information is not present. You can call the extension function explicitly:
Sorry for spamming the TypeScript list, but I made a slight change in my extension methods to support traits/mixins that might be interesting. When extension methods were in local scope, there was no good way (aka not using eval()) to dynamically add methods.
So, I encapsulated them in a _methods lexically scoped object:
var Iterable = new Trait({
// iterator() can be an extension method or a plain old function
map: (iterator) => (func) => { var iter = iterator.apply(this); ... }
});
var _methods = Extension.extend(_methods, String, {
iterator: () => { return new Iterator(this); }
});
// Bind all the trait methods and pass them to extend String
var _methods = Extension.extend(_methods, String, Iterable.bind(_methods.iterator));
_methods.map.apply("abc", [c => c.charCodeAt(0)]); // [97, 98, 99]
I can probably simplify extend() a bit more. The language this is targetting: Impulse-JS.
With the amount of library authoring going on and what seems like increased adoption of TS, now seems like a great time to dust this idea off and see if it's worth dedicating resources to!
Again, biggest thing is for libraries. I already find myself very badly wishing for some kind of idiomatic way to sprinkle some extensions around.
✏️
Reacted by Andrew Nosenko, Steve Beaugé, ViShadowFiend, Troy, Arad Alvand and Garrett R. Morris
I would personally be happy if Typescript had extension methods that used a distinct call syntax vs. normal method invocations.
For instance:
functionDate#toProto(): Timestamp{ ... }functionusage(){constproto: Timestamp=newDate()#toProto();// instead of toDateProto(new Date())}
This would allow
call chaining and fluent usage of extension functions. You can write foo.toBar().toBaz() instead of barToBaz(fooToBar(foo)).
easy discovery of extension functions through IDE autocomplete. new Date()# or new Date(). would show toProto() in the autocomplete list; selecting that item could auto import the extension function. Such functions might be more easily discoverable compared to toDateProto().
I suspect it would avoid some of the complications from #9 (comment). Making extension functions callable from JavaScript might require some attention, however.
What makes the #9 (comment) suggestion out of scope for TypeScript?
Extension methods seem like they should be resolved at compile-time based on typing information that's out of scope for ECMAScript. I'm not sure how ECMAScript could possibly provide the same level of utility through the bind operator.
functionFoo::toProto(): FooProto{ ... }functionBar::toProto(): BarProto{ ... }(newFoo())::toProto()// resolves to first method(newBar())::toProto()// resolves to second method
edit:
Reading into https://github.com/tc39/proposal-bind-operator, it seems the "::" would help a lot. However, in the above example, multiple "toProto" methods probably could not be imported into the same TypeScript file - they would have to be aliased.
[Do not] Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
Thanks. It seems the more active proposal is https://github.com/tc39/proposal-extensions. I asked here about the status of that proposal advancing to stage 2. Presumably TypeScript is not interested in introducing its own bind operator before there is consensus to add the operator to JavaScript.
I'd really appreciate some guidance.
I was playing around with ast transformers to see if I could have a custom extension method implementation (here: codibre/extension-methods#3).
I achieve making something like that work:
The extension declaration:
import{ExtensionMethod}from'extension-method';exportclassExtensionTest{
@ExtensionMethodstaticmyTest(str: string){return123;}}declare global {interfaceString{myTest(): number;}}
Where it is used:
import'./extension';console.log("abc".myTest());
In the test I'm running, it compiles to something like this:
"use strict";var__importDefault=(this&&this.__importDefault)||function(mod){return(mod&&mod.__esModule) ? mod : {"default": mod};};Object.defineProperty(exports,"__esModule",{value: true});constextension_1=__importDefault(require("./extension"));console.log(extension_1.ExtensionTest.myTest("abc"));
Which nails the idea, although there are many scenarios I need to explore.
The issue with this solution, though, is that the extension declared to String here is visible regardless of whether I've imported the extension file. Can I write the "type augmentation" somehow so the extension method will only be visible when I import the file where it is declared?
I see that the extension will only work if I import the extension file, which is ideal for the concept. However, if I could somehow have some "scoped argumentation" for existing types, I'd be able to reflect this behavior also in typescript, but I'm unaware of such a feature.
Activity
knazeri commentedon Jul 29, 2014
Highly suggested 👍
This would add methods to existing types without creating a new derived type or modifying the original type and it would definitely make sense in linq to typescript query operators
The original issue at codeplex had pretty much lots of votes:
https://typescript.codeplex.com/workitem/100
basarat commentedon Jul 29, 2014
👍
mpawelski commentedon Aug 13, 2014
👍
basarat commentedon Aug 14, 2014
Perhaps this can be used in an ambient context as well :
saschanaz commentedon Aug 14, 2014
extending A { }for short? I think we don't needclassthere.knazeri commentedon Aug 14, 2014
A couple of questions:
What would be the compiled js like? And how would be the runtime interoperability with the existing class?
Extension methods in C# are static methods with syntactic sugar for instance method call syntax with the first parameter specifying the type the method operates on. Which brings some benefits beside the instance call syntax:
Having considered
extension classsyntax, isextension classactually a class or a module at runtime? How do you translatethisin a body of an extension method at runtime? (Function.apply,Function.callmaybe?) And how would extension methods be like on an interface which I believe is the most commonly use case for extension methods?zlumer commentedon Aug 15, 2014
While the naive implementation of extension methods is probably like this:
Compiling to:
I think due to first-class functions in JS there is a problem with this approach:
If extension methods are to be implemented, there would probably have to be restirictions on accessing/changing their properties.
Considering "class" syntax
In my opinion, one of the common uses of extension methods is to make them work on interfaces, not just classes, so you could do something along the lines of
If possible, I think C#-like extension methods would work best:
Compiling to:
saschanaz commentedon Aug 15, 2014
Hmm, I thought that this proposal was just to extend prototypes.
Compiling to:
knazeri commentedon Aug 15, 2014
I don't think extension methods should be implemented by adding a function to the prototype! For one thing extending the prototype of built in types, is considered dangerous, 3rd party libraries may also add a method with the same name to the prototype, causing the behavior to depend on which JS file was loaded last, or worse you might overwrite the existing built-in methods! And for another, there's no way to have different method overloads for the extension methods defined in different modules!
More importantly, prototype extending works only for concrete classes, in a sense, no interface or declared class extension methods!
basarat commentedon Aug 15, 2014
I still agree with Kagami Sascha Rosylight (@saschanaz) . There are issues surely but for :
this would be caught by name collision in the lib's
.d.tsfile and yourcustom.d.tsfile.I don't expect it there to be any codegen for ambient declarations. PS: If you ambiently say something is a class TypeScript already assumes it has a prototype. e.g.
So adding to the ambient prototype is consistent with what already exists.
Perhaps examples of your concerns (if still there) would help.
knazeri commentedon Aug 15, 2014
This is only true if there is .d.ts metadata for every 3rd party library (which is not) and only when .ts files are being compiled together (ie. in the same project). Besides there are lots of .js libraries as part of server-side environment which are being injected into dynamic pages at runtime (take asp.net server controls for example)
Ambient class declaration does not have implementation, which is not the case for extension methods, unless extension methods for ambient class declarations are being erased in the compiled .js (which I believe is counterproductive to it's purpose), the program might break at runtime!
And how about interfaces? To extend an interface new methods has to be added to the interface, but changing the interface by adding new methods would break compatibility with existing consumers! Take
IEnumerable<T>in C# for example, I cannot imagine how many classes are already implementing this interface! Without having extension methods on interfaces, there could be no LINQ as we know it!I believe the real advantage of extension methods is being loosely coupled with the existing type, e.g.
The above example is supposed to add linq-like join operator for arrays, however having altered the prototype, we have lost the built-in join method. Having extension methods separated in a concrete different method actually benefits better method overloading.
There's also a big benefit with generic extension methods which is reducing the need for a common base class.
The following is an example of using generic extension method in C#:
Which you can simply use:
RyanCavanaugh commentedon Aug 15, 2014
Great discussion so far.
There's a key decision point here on this feature, which is how the emitted code would work. I see two options based on what's been discussed so far
Example code I'll use
Add to .prototype
Pros
y)Cons
prototypeis often a bad idea, especially for built-in objectsRewrite call sites
Pros
prototype, so it's safe for built-in typesCons
knazeri commentedon Aug 15, 2014
Ryan Cavanaugh (@RyanCavanaugh) I would alter the example code, to use the type parameter for the extension method instead of dynamic binding of
this:Which would translate to this:
So that the function behave like a static helper method and it's valid to call it non-instance way. However still fails when strong type information is not present. You can call the extension function explicitly:
151 remaining items
mikeaustin commentedon Apr 19, 2017
Sorry for spamming the TypeScript list, but I made a slight change in my extension methods to support traits/mixins that might be interesting. When extension methods were in local scope, there was no good way (aka not using eval()) to dynamically add methods.
So, I encapsulated them in a _methods lexically scoped object:
I can probably simplify extend() a bit more. The language this is targetting: Impulse-JS.
atrauzzi commentedon May 10, 2017
With the amount of library authoring going on and what seems like increased adoption of TS, now seems like a great time to dust this idea off and see if it's worth dedicating resources to!
Again, biggest thing is for libraries. I already find myself very badly wishing for some kind of idiomatic way to sprinkle some extensions around.
✏️
RyanCavanaugh commentedon May 10, 2017
Seems people aren't reading the thread.
Proposal class static block support (microsoft#43370)
fix: enable pnp for `typingsInstaller` and `watchGuard` (microsoft#9)
fix: enable pnp for `typingsInstaller` and `watchGuard` (microsoft#9)
reddaly commentedon Jul 15, 2024
I would personally be happy if Typescript had extension methods that used a distinct call syntax vs. normal method invocations.
For instance:
This would allow
foo.toBar().toBaz()instead ofbarToBaz(fooToBar(foo)).new Date()#ornew Date().would showtoProto()in the autocomplete list; selecting that item could auto import the extension function. Such functions might be more easily discoverable compared totoDateProto().I suspect it would avoid some of the complications from #9 (comment). Making extension functions callable from JavaScript might require some attention, however.
RyanCavanaugh commentedon Jul 15, 2024
Red Daly (@reddaly) out of scope for TypeScript. See https://github.com/tc39/proposal-bind-operator though
gonzojive commentedon Jul 26, 2024
What makes the #9 (comment) suggestion out of scope for TypeScript?
Extension methods seem like they should be resolved at compile-time based on typing information that's out of scope for ECMAScript. I'm not sure how ECMAScript could possibly provide the same level of utility through the bind operator.
edit:
Reading into https://github.com/tc39/proposal-bind-operator, it seems the "::" would help a lot. However, in the above example, multiple "toProto" methods probably could not be imported into the same TypeScript file - they would have to be aliased.
This might seem like a small detail, but the namespacing provided by a method receiver is helpful for keeping method names concise.
RyanCavanaugh commentedon Jul 26, 2024
That's type-directed emit. See https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals point 4:
gonzojive commentedon Jul 27, 2024
Thanks. It seems the more active proposal is https://github.com/tc39/proposal-extensions. I asked here about the status of that proposal advancing to stage 2. Presumably TypeScript is not interested in introducing its own bind operator before there is consensus to add the operator to JavaScript.
Farenheith commentedon Jan 20, 2025
Hi,
I'd really appreciate some guidance.
I was playing around with ast transformers to see if I could have a custom extension method implementation (here: codibre/extension-methods#3).
I achieve making something like that work:
The extension declaration:
Where it is used:
In the test I'm running, it compiles to something like this:
Which nails the idea, although there are many scenarios I need to explore.
The issue with this solution, though, is that the extension declared to String here is visible regardless of whether I've imported the extension file. Can I write the "type augmentation" somehow so the extension method will only be visible when I import the file where it is declared?
I see that the extension will only work if I import the extension file, which is ideal for the concept. However, if I could somehow have some "scoped argumentation" for existing types, I'd be able to reflect this behavior also in typescript, but I'm unaware of such a feature.
[wasm] checker: cover legacy null/undefined