Something like C# extension methods

Topics: Language Specification
Oct 31, 2012 at 5:47 AM
Edited Feb 14, 2013 at 6:57 AM
When I tried to extend the string class (using the prototype based way) I got a problem. The TS allows me to create interfaces but not to extend existent types. The CoffeScript does it using the following syntax:

String::startsWith = (prefix) => { ... }

wich will be translated to

String.prototype.startsWith = function(prefix) { ... };

In TS I had to do this:
var proto: any = String.prototype; 

proto.startsWith = function (prefix) { 
  return this.slice(0, prefix.length) == prefix; 
};

proto.endsWith = function (suffix) { 
  return this.slicde(-suffix.length) == suffix; 
}; 

interface String { 
  startsWith(prefix: string): bool; 
  endsWith(suffix: string): bool; 
} 
which is not exactly an elegant and safe code! ;)

== AFTER DISCUSSION =============================

I propose the following. We could have two kind of extensions:
  1. Extension methods (implemented like in C# way but it could have a more cool way of definition, I like the Kotlin way);
// C# way
function sum(this: Number, other: Number) {
  return this + other;
}

// Kotlin way ( I prefer this way)
function Number.sum(other: Number) {
  return this + other;
}

// Usage in TS:
var a = 10;
var b = 20;
var c = a.sum(b);
Translated to:
function Number$sum(_this, other) {
  return _this + other;
}

// Usage in JS:
var a = 10;
var b = 20;
var c = Number$sum(a, b);
  1. Prototype extension methods (which is the JS way of do the things);
function Number::sum(other: Number) { 
  return this + other;
}

// Usage in TS:
var a = 10;
var b = 20;
var c = a.sum(b);
Translated to:
Number.prototype.sum = function(other) {
  return this + other;
}

// Usage in JS:
var a = 10;
var b = 20;
var c = a.sum(b);
Oct 31, 2012 at 10:27 AM
Edited Oct 31, 2012 at 10:28 AM

See this answer: http://stackoverflow.com/questions/12701732/typescript-augmenting-built-in-types

looks like types are open ended anyway, so its easy to add extension methods on the fly

Coordinator
Oct 31, 2012 at 4:13 PM

Additionally, there is a limitation with extending types defined in lib.d.ts which is currently being worked on (http://typescript.codeplex.com/workitem/176).

Nov 1, 2012 at 3:00 AM
jstrachan wrote:

See this answer: http://stackoverflow.com/questions/12701732/typescript-augmenting-built-in-types

looks like types are open ended anyway, so its easy to add extension methods on the fly

Hey jstrachan,

Yes, the types are open by using interfaces but interfaces are not traits, so it can not have implementation, they are just signature. Thats why I had to create the implementation, in my code above, the then create the interface, so the compiler will now that I had extended the String type.

Jan 15, 2013 at 6:13 PM
Edited Jan 15, 2013 at 6:14 PM

In general, extending the prototype of built in types (such as String), is considered dangerous. Code beyond your control (such as 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, and inevitably causing lots of hard to diagnose bugs.

I think extension methods should be implemented as they are in C#, as static functions that are called via an instance call syntax sugar, but ultimately transformed back into static calls by the compiler. This also enables you to "plug in" your extension methods only in the scope in which you need them, and completely prevents conflicts with 3rd party code.

Jan 17, 2013 at 4:02 AM

I agree with you MgSam, but sometimes its a feature needed.

Feb 14, 2013 at 6:44 AM
So, with your observation we could have two kind of extensions:
  1. Extension methods (implemented like in C# way but it could have a more cool way of definition, I like the Kotlin way);
// C# way
function sum(this: Number, other: Number) {
  return this + other;
}

// Kotlin way ( I prefer this way)
function Number.sum(other: Number) {
  return this + other;
}

// the closure definition of the last could be
var func : Number.sum(Number) => (other) => this + other;
  1. Prototype extension methods (which is the JS way of do the things);
function Number::sum(other: Number) { 
  return this + other;
}
Feb 14, 2013 at 1:48 PM
Edited Feb 14, 2013 at 8:00 PM
I should add another benefit to having extension methods really be syntax sugar for calling static methods. They can be added to interfaces. This would be especially useful in TypeScript, because interfaces are the only place you can define indexers (not classes). So if you want a type that has both an indexer and some methods associated with it, interface + extension methods would be the perfect solution.
Mar 26, 2013 at 8:00 PM
One of the big issues with having extension methods that are implemented as static methods is the fact that typescript falls back to "any" when a type can't be inferred, and it also supports sub typing.

This could make interactions of the above features and extension methods non-obvious in many cases.

Type inference gotcha:
The dispatch to a static method would only work based on the static type information available, but if a variable holding a number were typed as "any" it would suddenly "lose" the sum method.

Sub-typing gotcha:
If "MethodA" accepted a base class "ClassA" which had an extension method "Extension", but there were also a sub class of "ClassA" named "ClassB" with a new, overridden extension method "Extension". If "MethodA" uses the "Extension" method, and you call it with an instance of "ClassB" the overridden version of "Extension" isn't called.

Generics:
The above issues also come in to play for any generic type parameters in new versions of typescript. Type erasure and sub typing could make dispatching extension methods confusing on generic types.

I'm not saying it wouldn't be great to have these features, but there are a lot of subtle implications that could confuse people if they aren't thought through carefully.

As much as I don't like using syntax to solve a problem, a new dispatch for extension methods would at least give the compiler a fighting chance of notifying us of potential issues. Something like:
var n = (10)::sum(20);
May 8, 2013 at 4:18 AM
C# style extension methods, which would entail that the prototype of the extended object remain unchanged, would be of huge value to developers. Even given the implementation difficulties concerning type inference, I still that this solves a very real problem in the JavaScript developer community.
May 12, 2013 at 8:57 AM
MgSam wrote:
In general, extending the prototype of built in types (such as String), is considered dangerous. Code beyond your control (such as 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, and inevitably causing lots of hard to diagnose bugs.
I think this does not apply to Typescript. If you mix javascript and typescript, yes you are correct, but strictly in typescript this cannot happen, since the compiler will detect when such a overwrite of the same prototype name happens and give a proper error.
May 25, 2013 at 7:50 AM
@manast, I definitely see your point. TypeScript is adding type annotations, not extending the languages. In general when the langauge is extended, it is based on the EcmaScript 6 specification, if I am not mistaken.

But seeing as how TypeScript is a superset of JavaScript, it is definitely possible to assign members to existing prototpes. For instance:
var ArrayPrototype: any = Array.prototype;
ArrayPrototype.shuffle = function() {
    throw 'not implemented';
}
Still the value of having a tool which would allow us not to extend build in types, but would allow us to code as if we had, would be a wonderful thing.

Best,
Doug Rubino
May 30, 2013 at 11:37 PM
I think we all want extension methods.

Why not vote up the issue: https://typescript.codeplex.com/workitem/100
May 31, 2013 at 6:06 PM
I really don't want extension methods and would vote down the workitem if I could.

Having the compiler examine the type name to decide what static extension method to call runs counter to structural typing. It also causes some of the troubling issues dlshryock raised.

Defining an interface and then added to the prototype is only slightly awkward, which is fine considering how little you should be doing it anyways.
I think this does not apply to Typescript. If you mix javascript and typescript, yes you are correct, but strictly in typescript this cannot happen, since the compiler will detect when such a overwrite of the same prototype name happens and give a proper error.
It's not an error to replace a function in the prototype with another of the same signature.
Jun 3, 2013 at 9:18 PM
Edited Jun 4, 2013 at 12:29 AM
-post deleted by author-
Jun 3, 2013 at 9:34 PM
@robfe Extension methods are useful precisely for the (extremely common) scenario of having 3rd party .js files present. It isn't safe in these scenarios to modify the prototype, so a syntax sugar for calling static methods would be the safest way of "adding" to existing types.

@dlshryock raises some good points, but these seem relatively minor issues to me vs the benefit achieved. Good IDE support whereby the completion list clearly indicates extension methods differently from "normal" methods eliminates most confusion here. Variants of these same issues already exist in C#, and it hasn't proven to be much of a problem there.

TypeScript is principally about providing static, compile-time features and checking. It makes sense they would only be expected to work when the static type information is actually available to the compiler.
Jun 3, 2013 at 9:43 PM
@MgSam

Don't get me wrong, I like the idea of adding extension methods.
What bugs me is that when a type falls back to 'any', and you try to call an extension method, it won't be a compile time error, you'll have to wait until runtime to find out that the method doesn't exist. Your IDE can't help you here, because every method appears valid on an 'any' reference.
Jun 3, 2013 at 9:47 PM
Hopefully, with generics and the future typing of the this pointer, there will be very few instances where you'd need to use any. Either way, if you're using any, you're deferring the type checking until runtime, so you'd expect that's when you'll find your errors.
Jun 3, 2013 at 11:17 PM
I don't think adding extension methods to TypeScript amounts to "adding C# to TypeScript". That's a silly way of looking at things anyway. We should have nice language features if we can have them.

Also, I reject the notion that we will have any compiler problems from extension methods. The compiler and the code completion facility already works perfectly when basic prototypes are extended. Why would it not work with actual implementations of those methods as well?

Lastly, modifying a prototype is probably one way of achieving extension methods in TypeScript, but why pretend that this is the actual method that will be chosen if extension methods are added to TypeScript? I would think that a good way will be chosen, not some bad way someone comes up with and then attacks. Give the language implementers some credit... (and the idea that "all the benefits of namespaces will be lost" falls under this same point too).