85

Closed

Extension methods

description

In Javascript you can add functions and other types of members to objects by adding them to the prototype.

The adding of functions (even accessor properties) to objects could be simplified in TypeScript with a language extension similar to
Extension methods in C#. Pro is that you don't need to write weak code to extend types.

Extension methods could be stored in modules and available as they are imported.
Closed Jul 28, 2014 at 11:17 PM by jonturner
As part of our move to GitHub, we're closing our CodePlex suggestions and asking that people move them to the GitHub issue tracker for further discussion. Some feature requests may already be active on GitHub, so please make sure to look for an existing issue before filing a new one.

You can find our GitHub issue tracker here:
https://github.com/microsoft/typeScript/issues

comments

jbaroncom wrote Oct 5, 2012 at 6:06 PM

Or what about making Interface and Classes open (just like Modules are now). For example allow for something like the following:

interface String extends String {
endsWith(str:String) : bool;
}

ahejlsberg wrote Oct 6, 2012 at 9:35 AM

Interfaces are open ended in TypeScript, so you can do this already,

jbaroncom wrote Oct 7, 2012 at 7:42 AM

Yes I did indeed find out that interfaces are open ended. Just define the interface again and only put the additional methods in there. A very nice features indeed!!!

MrKishi wrote Oct 8, 2012 at 8:31 PM

Somewhat related (and also related to http://typescript.codeplex.com/discussions/398062): what about a way to implement methods (extension methods?) for interfaces?

Right now, you need to create an independent function if you want to do something with any type that implements an interface.

e.g.:
// lib1
class/interface Vector { x: number; y: number; }

// lib2
class/interface Point { x: number; y: number; }

// my code
interface point { x: number; y: number; }

function length(p: point) { return Math.sqrt(p.x * p.x + p.y * p.y); }


You could add 'length' to the interface but that would force every Point implementation to have its own length method. As length is generic enough and only uses the public Point interface, making it a function allows you to use it on any point. In this case, I can use both libraries' equivalent of a point in my length function.

The problem is that you've now lost the IDE support for code completion, which is a big deal for TypeScript (or I would think so, given the type system is only there for tooling/compilation). There's no way to know every function on the generic Point implicit implementation.

I feel like there's incentive torwards the use of interfaces in TypeScript due to their open-endedness and structural typing. You can start with interfaces and keep building on them and independent functions until you need private state, that way all the public-state functions will work on any implementation, and the private state is obviously restricted to specific classes.

But perhaps allowing us to implement methods directly for interfaces would make them even more reusable and interoperable between libraries. Classes (brands) would be the stricter and practical (with private members and so) implementations while interfaces would still have behavior shared by all implementations.

Consider something like:

// lib1
class/interface Vector { x: number; y: number; }

// lib2
class/interface Point { x: number; y: number; }

// my code
interface point {
x: number; y: number;
length() { return Math.sqrt(this.x * this.x + this.y * this.y); }
}

Now every Point and Vector could get a 'length' method with code completion.

There are details to be studied such as what code should be generated and how to deal with duplicate definitions but I think there are several ways to approach them (from some restrictions to even new syntax constructs), given the idea/direction itself isn't some PL blasphemy. I'm not a PL engineer after all.

Any thoughts on that?

blake05 wrote Oct 16, 2012 at 3:52 PM

Nub here....

I'd love to extend String with IsNullOrEmpty. adding it to the interface is easy but then what does one have to do to implement the interface? I just want to implement my IsNullOrEmpty.. But I can't get that to work. I did something like this: http://stackoverflow.com/questions/12706290/typescript-define-methods-in-separate-files but it won't even compile... It also doesn't feel natural or correct...

interface String {
IsNullOrEmpty(value: string): bool;
}

class StringExtensions implements String {
toString: () => string;
charAt: (pos: number) => string;
charCodeAt: (index: number) => number;
concat: (...strings: string[]) => string;
indexOf: (searchString: string, position?: number) => number;
lastIndexOf: (searchString: string, position?: number) => number;
localeCompare: (that: string) => number;
match: (regexp: string) => string[];
match: (regexp: RegExp) => string[];
replace: (searchValue: string, replaceValue: string) => string;
replace: (searchValue: string, replaceValue: (substring: string, ...args: any[]) => string) => string;
replace: (searchValue: RegExp, replaceValue: string) => string;
replace: (searchValue: RegExp, replaceValue: (substring: string, ...args: any[]) => string) => string;
search: (regexp: string) => number;
search: (regexp: RegExp) => number;
slice: (start: number, end?: number) => string;
split: (seperator: string, limit?: number) => string[];
split: (seperator: RegExp, limit?: number) => string[];
substring: (start: number, end?: number) => string;
toLowerCase: () => string;
toLocaleLowerCase: () => string;
toUpperCase: () => string;
toLocaleUpperCase: () => string;
trim: () => string;

length:() => number;

// IE extensions
substr: (from: number, length?: number) => string;


public IsNullOrEmpty(value: string) {
    return value == null || value.length == 0;
}
}

MgSam wrote Jan 15, 2013 at 6:08 PM

I don't think extension methods should be implemented by adding a function to the prototype. This would make them not much different than what is available in vanilla JS.

In JS, adding methods to the prototype of objects (especially built in objects), is dangerous, as it can cause 3rd party libraries to break without your knowledge.

I think the value of extension methods is that they are actually static methods that are called via a syntactic sugar that the compiler ultimately erases. It would be far more beneficial to implement TypeScript extension methods in this way, so you can safely "extend" built in types, without risk of breaking code you don't own.

LukeH wrote Feb 26, 2013 at 6:04 PM

This issue thread has discussed a couple of related topics. For tracking purposes, I'm going to interpret this particular issue as a suggestion to add C#-like extension methods, which would behave roughly as follows:
// TypeScript
module StringExtensions {
    export extension endsWith(this: string, str: string): bool { return false; }
}
"hello".endsWith("lo");

// Generated JavaScript
var StringExtensions;
(function(StringExtensions) {
  StringExtensions.endsWith = function(str) { return false;}
})(StringExtensions || (StringExtensions = {}))
StringExtensions.endsWith.call("hello", "lo");
Specifically - although the TypeScript code could be written as a property lookup on the receiver object, this could in cases where the typed property lookup fails, but there is a module in scope which exports an extension function with a 'this' parameter of the correct type, instead resolve to a call to the module exported function bound to the receiver object.

There are a few reasons this sort of feature is not very natural for TypeScript; (1) we aim to not do expression level rewriting of code to make sure that semantics are closely aligned with JavaScript (2) this relies very heavily on type information for the semantics of the code - if the receiver was 'any', we couldn't resolve the extension method - which means subtle bugs could happen when a piece of code is loosely typed in some local piece of code.

Given these concerns, I do not currently expect this is something we would add to TypeScript in the near term, so I'll move into 2.x timeframe for now.

Scriby wrote Apr 30, 2013 at 9:06 AM

One thing that would be useful for me is this construction:
module AsyncblockExtensions {
    export extension sync(this: T): T {}
}
That construction would allow me to provide strong typing for some syntax rewriting that asyncblock does:
    var contents = fs.readFile('test.txt', 'utf8').sync();
I understand that this use case is a bit off the beaten path and not something TypeScript would specifically try to support, but just providing some feedback.

mindplay wrote Aug 29, 2013 at 8:21 PM

Extension methods in C# are static methods with syntactic sugar for object-like call syntax - there is no this during a static method call.

I believe the following would be closer to C# in terms of syntax:
// TypeScript
module StringExtensions {
    export function endsWith(this value: string, ending: string): bool { ... }
}
"hello".endsWith("lo");
StringExtensions.endsWith("hello", "lo");
Notice how you can now call the function and pass arguments to it, if you prefer - similar to C#.

As for the compiled output, the following might be closer to C# in terms of semantics:
// Generated JavaScript
var StringExtensions;
(function(StringExtensions) {
  StringExtensions.endsWith = function(value, ending) { ... }
})(StringExtensions || (StringExtensions = {}))
StringExtensions.endsWith("hello", "lo");
StringExtensions.endsWith("hello", "lo");
Notice the compiled output is now identical to a manual invocation.

If somebody wants to manually invoke an exported function from JavaScript, I think being able to call the function like a regular function (without forcing a calling context) is much more natural and comfortable?

I know we're not just trying to copy C# for the sake of copying C#, but this approach seems simpler and more flexible - a function is just a function, and the addition of the this keyword to the parameter makes the object-style syntax available, but you can still call the function like any regular function, if you need or wish. The compiled output is simpler and more closely resembles something hand-written.

onyxring wrote Sep 6, 2013 at 9:34 PM

Like some of the posters above, I too have been trying to find a way to model prototype extensions for native types. For example, I'm using a library that extends String to support String.format(...) and String.isNullOrEmpty(...) but it isn't possible to create a definition file for this because the default lib.d.ts (in 1.9.1) defines the String variable in a way that cannot be extended :
      declare var String { ... }
Rather than modifying the Typescript compiler to handle prototypes differently, could we just change the native types declaration to inherit from an interface, a la:
      interface StringStatic{ ...  }
      declare var String : StringStatic;
That way we wouldn't have to include modified versions of the lib.d.ts file to support extending native types.

LunaC wrote Dec 8, 2013 at 8:08 PM

My recommendation

I'm always looking from the perspective that one day TS and Dart will have native VMs so the loose prototype requirement isn't optimal. Minimal language impact but descriptive enough for future compile targets.
class MyStringExtensions extends interface String{
    startsWith(str:String): boolean{
        return (this.indexOf(str) === 0);
    }
}

compiles to something like this:
interface String {
    startsWith(str:string): boolean;
}

String.prototype.startsWith = function(str){
    return (this.indexOf(str) === 0);
}

As you can see it MyStringExtensions couldn't be constructed but allows containing the code should you later want to target C# for native execution

-Craig

SaschaNaz wrote Jul 1, 2014 at 9:50 AM

Direct prototype extension is not recommended because of performance problem for native prototypes, however...

SaschaNaz wrote Jul 1, 2014 at 9:55 PM

Sorry, the document seems not to be saying about prototype extension but prototype chaining. Maybe my statement is false...