Code Monkey home page Code Monkey logo

Comments (164)

zlumer avatar zlumer commented on March 28, 2024 9

While the naive implementation of extension methods is probably like this:

extension class Shape { // Syntax??
    getArea() { return /* ... */; }
}
console.log(x.getArea()); // OK

Compiling to:

// js
var _Shape = {
    getArea:function() { return /* ... */; }
}
console.log(_Shape.getArea.call(x));

I think due to first-class functions in JS there is a problem with this approach:

extension class Shape { // Syntax??
    getArea() { return /* ... */; }
}
var x = new Shape();
var y = new Shape();
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.

Considering "class" syntax

extension class Shape { // Syntax??
    getArea() { return /* ... */; }
}

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

interface IPoint {
    x:number;
    y:number;
}
class Point implements IPoint {
    constructor(public x,public y) {
        // ...
    }
}
var p1:Point = new Point(0, 50);
var p2:IPoint = { x:32, y:32 };

console.log(p1.getLength()); // extension method
console.log(p2.getLength()); // same extension method

If possible, I think C#-like extension methods would work best:

extension function getLength(this p:IPoint):number {
    return Math.sqrt(p.x*p.x + p.y*p.y);
}
// another extension method with the same name but for different class
extension function getLength(this s:string):number {
    return s.length;
}

console.log(getLength); // compiler error
console.log(p1.getLength); // Function or undefined or compiler error?
console.log(p1.getLength.foo); // compiler error
p1.getLength.foo = "bar"; // compiler error

Compiling to:

// js
function _IPoint$getLength() {
    return Math.sqrt(this.x*this.x + this.y*this.y);
}
function _string$getLength() {
    return this.length;
}
console.log(_IPoint$getLength.call(p1)); // OK
console.log(_IPoint$getLength.call(p2)); // OK

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024 7

Because TypeScript is not C# and JavaScript is not the CLR.

Here's some code that people would very reasonably expect to work; none of these would be possible if we tried to extension methods with call site rewrites.

// Setup
class Rectangle {
  constructor(public width: number, public height: number) {}
}
// Do not be distracted by exact syntax here
extension function getArea(this s: Rectangle ) {
  return s.width * s.height;
}
// end setup

// Challenge course begins
interface HasArea {
  getArea(): number;
}

function fn1(x: HasArea) {
  console.log(x.getArea());
}
fn1(new Rectangle(10, 10));

var a: any = Math.random() > 0.5 ? new Rectangle(3, 3) : { getArea: function() { return 3; } };
console.log(a.getArea());

var s = new Rectangle(5, 5);
var f = s.getArea.bind(s);
console.log(f);

class Square {
  constructor(public size: number) { }
  getArea() { return this.size * this.size; }
}
var box: Square|Rectangle = /* ... anything ...*/
console.log(box.getArea());

box.getArea.call(box);

var boxes: HasArea[] = [new Square(5), new Rectangle(10, 10)];
boxes.forEach(b => {
  console.log(b.getArea());
});

The only plausible language in which we rewrote call sites would have the following restrictions:

  • You can't use extension methods as first-class objects, even though that's a very common JavaScript pattern. Only immediate invocations are allowed
  • Extension methods aren't used when checking assignability, so you can't use them to add new functionality that is used with any indirection. You can't use an extension method to 'fix' a type to match some interface, even though that's a primary use case
  • Extension methods aren't visible when put in a union type, which everyone will assume is a compiler bug
  • If the type of a variable accidentally becomes any, your code will break at runtime with an error you won't be able to diagnose via debugging the code itself. This will look like a compiler bug to most people
  • You can't use TypeScript extension methods from JavaScript without knowing the special code to write

When you add up all those restrictions, the only real advantage foo.extMethod() has over extMethod(foo) is that there's an extra dot in the extension method call. Plus, it creates a giant trap by having extension method calls on any variables fail. That's not a good deal.

from typescript.

knazeri avatar knazeri commented on March 28, 2024 5

@RyanCavanaugh I would alter the example code, to use the type parameter for the extension method instead of dynamic binding of this :

class Square { /* ... */ }
extension function getArea(this square: Square) { // First param must be 'this'
    return square.width * square.height;
}
var x = new Square(10, 10);
console.log(x.getArea());

Which would translate to this:

function getArea(square) {
    return square.width * square.height;
}
var x = new Square(10, 10);
console.log(getArea(x));

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:

var x = new Square(10, 10), y:any = x;
console.log(x.getArea()); // Succeeds
console.log(getArea(y));  // Succeeds

from typescript.

knazeri avatar knazeri commented on March 28, 2024 4

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

from typescript.

ronzeidman avatar ronzeidman commented on March 28, 2024 4

+1
I think the extension methods should support interfaces and be a syntactical sugar like in C#:

interface IMovable {
    position: Point;
}
extension class MyExtensions {
    move(this moveable: IMovable, newPoint: Point) {
...
    }
}
var car: Movable = {...};
car.move(newPoint);

this will compile to:

var MyExtensions = {
      move...
}
var car = {...};
MyExtensions.move(car, newPoint);

from typescript.

knazeri avatar knazeri commented on March 28, 2024 3

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!

from typescript.

knazeri avatar knazeri commented on March 28, 2024 3

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#:

public static bool In<T>(this T obj, params T[] source)
{
    return new HashSet<T>(source).Contains(obj);
}

Which you can simply use:

var o = 1;
o.In(1, 2, 3, 4, 5); /// true

from typescript.

danfma avatar danfma commented on March 28, 2024 3

Why not just use something like the kotlin syntax?

// some class A
class Hello {
    language: string;
}

extension Hello.printHello(name: string) {
    return "Hello, " + name + " in " + this.language;
}

from typescript.

oliverw avatar oliverw commented on March 28, 2024 3

Reading this discussion I still do not understand why extension methods can't be implemented just like the static C# extension methods. I mean why couldn't the compiler could just emit invocation of the static method?

from typescript.

gabrielPeart avatar gabrielPeart commented on March 28, 2024 3

I believe the Extensions implemented in Swift 2.1 is a very good example of how it could be implemented (as initial proposal at least)

Will allow us to do something like the following example here

from typescript.

basarat avatar basarat commented on March 28, 2024 2

πŸ‘

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024 2

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

class Square { /* ... */ }
extension function getArea(this: Square) { // First param must be 'this'
    return this.width * this.height;
}
var x = new Square(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() {
    return this.width * this.height;
}
var x = new Square(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() {
    return this.width * this.height;
}
var x = new Square(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

from typescript.

ivogabe avatar ivogabe commented on March 28, 2024 2

I like the suggestion of @KamyarNazeri, this can be used on an enum (or an union in the feature) too. This code is also faster than a prototype call or a .call().

Would the extension keyword be necessary? Since you already add this to the first argument.

from typescript.

 avatar commented on March 28, 2024 2

-1 Humans cannot understand code flow using extension methods.

(-1'ing everything is not really helping)

There are countless real world use-cases where extension methods are proven to be useful.

If you are unsure about the significance of extension methods, take a look at C#.

If you don't like the way it is proposed to be implemented, please provide an alternate approach.

Extension should be declarative feature.

Yes and this is in the very first line of this proposal.

from typescript.

mpawelski avatar mpawelski commented on March 28, 2024 1

πŸ‘

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024 1

Maybe we can choose separate syntax to call those functions.

class Square {
  foo() {
  }
}
function getArea() joins Square {
  return this.width * this.height;
}
var x = new Square(10, 10);
var y: any = x;
console.log(x calls getArea()); // Succeeds
console.log(y calls getArea()); // Succeeds both in compiling and in runtime.
console.log(x.getArea()); // Fails, this is only for functions in prototype
console.log(x calls foo()); // Fails, this is only for extension functions

PS: Fixed to allow y calls getArea().

from typescript.

knazeri avatar knazeri commented on March 28, 2024 1

This turned to be more complicated that it looked like! Having rewrite call sites comes handy when coding, however I agree that the code might break at runtime! I've seen lots of javascript developers debugging their code in the browser developing tools which also fails using rewrite call sites.
But the ability to extend interfaces is so good that you can't simply ignore it, so probably we are addressing two different problems here:

  • Extending a class (by using extension methods)
  • Extending interfaces (including built-in types since built-in types are represented as interfaces in the typescript runtime)

Maybe we could consider Java 8 default methods (or as they are often called defender methods) to extend an existing interface. Of course default methods in an interface could translate back to prototype in the emitted js code:

interface ISquare {
    width: number;
    height: number;
    default getArea(): number {
        return this.width * this.height;
    }
}

class Square implements ISquare {
    constructor(public width: number, public height: number) {
    }
    // no need to implement getArea here
}

var x = new Square(10, 10), y: any = x;
console.log(x.getArea());  // Succeeds
console.log(y.getArea());  // Succeeds

which would compile to:

var Square = (function () {
    function Square(width, height) {
        this.width = width;
        this.height = height;
    }
    Square.prototype.getArea = function () {
        return this.width * this.height;
    };
    return Square;
})();

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024 1

Maybe call the function like this: (since this syntax already exists)

Chaining would become uncomfortable in that case. Example:

var num = new Number(0);
function plus(input: number) joins Number {
  return this.valueOf() + input;
}
num.plus(3).plus(3).plus(3);
num calls plus(3) calls plus(3) calls plus(3); // Well... not very good to see. Hmm

versus

plus(plus(plus(num, 3), 3), 3);

...while the rewritten JavaScript will be anyway plus(plus(plus ... form.

Edited:

num->plus(3); // Syntax that looks like the C++ one but for extensions?

or

num<-plus(3); // function 'plus' joins to 'num'.

from typescript.

zlumer avatar zlumer commented on March 28, 2024 1

For the record, here's an example of runtime exceptions in C#:
http://stackoverflow.com/questions/5311465/extension-method-and-dynamic-object-in-c-sharp
While TypeScript is certainly far more likely to have untyped variables than C# (I myself have about 60% variables untyped in my TS code and only about 10% untyped in C# code), this can be considered a known limitation. Maybe extension methods should only work with a "--noImplicitAny" compiler flag to decrease the possibility of a runtime error.

Personally I'm strongly against any silent prototype modifications - changes to any existing prototype (everything except initial class-defining code generated by TS compiler) should always be explicit, having programmer manually type "class.prototype" to prevent at least some part of the errors.
Having said that, there's absolutely no reason to introduce new syntax for prototype extending: you can always do Square.prototype.getArea = /*...*/ and get all the syntactical benefits of such approach. If "rewriting call sites" is considered inappropriate, I think it's best to not introduce extension methods at all, than confuse programmers with a very dangerous silent prototype-changing feature.

Extension methods are nothing more than syntax sugar for method calls (at least that's how I see it and how they are implemented in C#). While certainly useful, they are not necessary.

from typescript.

electricessence avatar electricessence commented on March 28, 2024 1

To start you make it like C# and worry about syntax sugar later.

module A {
  class B {
    c():void {}
  }

 // somewhere else.
 module D {
 // Extend.
  static d(this target:B):void {}
 }

}

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024 1

Yes, C#/Java users want rewriting call sites, but it seems there's no good way to do that. So I still think we may need third-party way to implement really-JS-like extension methods, at least until ECMA people do something here.

interface ObjectStatic {
  bindExt(extensionFn: (base: this, ...args:any[]) => any); // Still from potential third-party library
}

Object.prototype.bindExt = function (extensionFn: (base: this, ...args: any[]) => any) {
  return (...args: any[]) => extensionFn(this, ...args); // ES6 Spread
}

(Burrowing this type concept from #289)

Now there should be no dynamic script loading, while I don't like the ...args: any[] part.

class Foo { /* */ }
function fooExtensionMethod(base: Foo, arg1: number, arg2: string) { /* */ }

var foo: Foo;
foo.bindExt(fooExtensionMethod)(arg1, arg2);

I think this will provide C#/Java-like extension method. No additional syntax, no rewriting based on type info, but still it gives extensions.

from typescript.

jbondc avatar jbondc commented on March 28, 2024 1

@RyanCavanaugh Have you looked into using a ~ proxy:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Not sure where that proposal stands but fairly optimistic this can work:

class Rectangle {
  constructor(public width: number, public height: number) {}
  static foo() {
      return 'foo';
   }
}
Object.seal(Rectangle);
extends(Rectangle, {
  static bar(){  return 'bar'; }
  getArea() {
  return this.width * this.height;
});

Emits

class Rectangle {
....
}
proxy_Rectangle = new Proxy(Rectangle,  {
    get: function(target, name){
        var extension = {
           bar: function() { return 'bar'; }
        } 
        return  name in extension ? extension[name] : target[name]
    }
});

proxy_Rectangle_new_handler =  {
    get: function(target, name){
        var extension = {
           getArea: function() { return this.width * this.height; }
        } 
        return  name in extension ? extension[name] : target[name]
    }
});

// All futur references to 'Rectangle' are wrapped by the proxy
function fn1(x: HasArea) {
  console.log(x.getArea());
}
fn1(new Proxy(new Rectangle(10, 10), proxy_Rectangle_new_handler));
proxy_Rectangle.bar()

from typescript.

ronzeidman avatar ronzeidman commented on March 28, 2024 1

@RyanCavanaugh The example in #9 (comment) is a good one. I've now read the entire thread and there were great suggestions, I like the ES7 bind operator (::) but I suggest something a bit different -
introducing an "Elixir" like pipe operator "|>":

function getArea(x: Rectangle) { ... }

var myRectangle = new Rectangle();
myRectangle |> getArea();

it will make things like "extending" array functions much easier and much more readable without changing the prototype:

grouppedReduce(groupBy(mySort(myArray.map(...), ...).filter(...), ...), ...);
myArray.map(...)|>mySort(...).filter(...)|>groupBy(...)|>grouppedReduce(...);

What do you say?

from typescript.

hdachev avatar hdachev commented on March 28, 2024 1

Hey just want to express my +1 for @ronzeidman -style extension classes with a few more words. I fully realize that this is something that will differentiate TS from ES, since it's impossible to pull it off without a type system, but I also think that this is the sort of thing that leverages the type system for an amazing gain in developer experience, and it makes perfect sense for our language.

ES will obviously never get a type system which means it will never be able to get this feature either, while we can easily have and love it every day. It transpiles to ok looking JS, and to anyone thinking otherwise, well today I have to type a lot of the same stuff manually on a daily basis. Re: worries that methods won't behave as usual when deferenced or tested for existence, etc, I really don't see this a major problem, since that is rather niche usage wrt method calling. As such those can result in a compiler error and I just don't see how that can become a problem.

Aside from the obvious case for interfaces, it'd allow one to extend collection types like MyType[] that just cannot be done otherwise sensibly. Underscore-like utility libraries will become a syntactic bliss. I imagine it'll play very nicely with this-typing for building fluent apis too.

Brief - life will be greater, and more beautiful, and so much more fantastic! We have a type system, there's nothing wrong in using it to make our lives even better. This feature relies entirely on the type system, and as such, in my opinion, is a perfect fit for TS.

from typescript.

 avatar commented on March 28, 2024 1

Class extension would be useful for separated definition members with different access level:

class A {}

private extension class A {
    func1() : void =>  {}
}

protected extension class A {
    func2() : void =>  {}
}

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024 1

Given the constraints imposed by emit (see #9 (comment)), there are realistically only two paths forward for object-first non-property method invocations:

  • Partial classes (#563)
  • ES20xx 'bind' operator (#3508)

I think we're reasonably satisfied (minus bikeshedding) on partial classes, and these can already be emulated today with a not-distasteful syntax (elucidated here: #563 (comment)). I'd encourage anyone to weigh in meaningfully on extension vs partial vs other keywords, keeping in mind that we didn't just say "yea partial sounds good" and be done with it.

The ES20xx 'bind' operator ('xx' because who knows what version) is still in semantic flux and we need that proposal to either die cleanly (to let us lay clear claim to that syntactic space), or proceed with clear semantics, before we can act on it. It's an area where TypeScript can't safely act unilaterally. There's good discussion going on at tc39/proposal-bind-operator#24 and its parent repo that outlines next steps for the proposal and it'd be great for people to perform citizen advocacy at ESDiscuss or other forums to help this move forward.

from typescript.

basarat avatar basarat commented on March 28, 2024

Perhaps this can be used in an ambient context as well :

declare module foo{
    class A{}
    extension class A{} 
}

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

extending A { } for short? I think we don't need class there.

from typescript.

knazeri avatar knazeri commented on March 28, 2024

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?

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

Hmm, I thought that this proposal was just to extend prototypes.

class A {
    foo() {

    }
}
extending A {
    bar() {

    }
}

Compiling to:

var A = (function () {
    function A() {
    }
    A.prototype.foo = function () {
    };
    return A;
})();
A.prototype.bar = function () {
};

from typescript.

basarat avatar basarat commented on March 28, 2024

I still agree with @saschanaz . There are issues surely but for :

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.

declare class Foo{}
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.

from typescript.

knazeri avatar knazeri commented on March 28, 2024

@ivogabe +1 for mentioning enum, I would like to also mention that using static functions, you can call methods on objects that are null, e.g. isNullOrEmpty for string type. small things yes, but darn useful

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

How about this?

class Square { /* ... */ }
function getArea() joins Square {
    return this.width * this.height;
}
var x = new Square(10, 10);
console.log(x.getArea());

Translates to:

/* Regular emit for 'Square' here */
__Square_getArea = function(_this) {
    return _this.width * _this.height;
}
var x = new Square(10, 10);
console.log(__Square_getArea(x));

PS: Or without renaming, as @KamyarNazeri did. I like the non-instance way.

function getArea(_this) {
    return _this.width * _this.height;
}
var x = new Square(10, 10);
console.log(getArea(x));

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

I want to emphasize the danger of the "rewrite call sites" approach:

class Square { /*... */ }
/* Extension method getArea, syntax irrelevant */
function process(callback: (n: Square) => void) {
    /* invoke callback on a variety of Squares */
}

process((n) => {
    n.getArea(); // Succeeds
});
process(((n) => {
    n.getArea(); // No compile error, fails at runtime
}));

var x = new Square(), y: any;
var arr1 = [x];
arr1[0].getArea(); // Succeeds
var arr2 = [x, y];
arr2[0].getArea(); // No compile error, fails at runtime

There are very strong reasons TypeScript avoids rewriting code based on type information (see https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals). No one wants to get in a situation where adding a non-observed element to an array or adding extra parentheses around a function could causes program to start to fail at runtime.

from typescript.

knazeri avatar knazeri commented on March 28, 2024

@RyanCavanaugh Seriously I don't understand why by adding extra parenthesis around a lambda function, the runtime infers variable n as any?
It's like to evaluate variable b's type argument in the following code as any (which is not)

var a = function (n: number): void { }
var b = (a);  // here b accepts number!

As for the latter example, I believe no-one expects an extension method to work when boxing variable to another type (here any due to the type mismatch in the array). At least this is how C# / VB work, with boxing to another type, there's no extension method! Only they don't compile in such situations.

from typescript.

danquirk avatar danquirk commented on March 28, 2024

We explicitly decided parenthesizing an expression should be the way to opt out of contextual typing or else you were always at the mercy of the inference algorithm even if it was wrong/incomplete.

from typescript.

basarat avatar basarat commented on March 28, 2024

default methods in an interface could translate back to prototype in the emitted js code

This interface looks more like an abstract class. #6

from typescript.

knazeri avatar knazeri commented on March 28, 2024

This interface looks more like an abstract class.

Only a class might already inherit another base class, I think the real benefit with interface extension comes with multiple inheritance simulation.

from typescript.

ivogabe avatar ivogabe commented on March 28, 2024

Maybe we could consider Java 8 default methods

The difference is that interfaces in TypeScript can be used without classes. How would the following code compile?

interface ISquare {
    width: number;
    height: number;
    default getArea(): number {
        return this.width * this.height;
    }
}
var square: ISquare = {
    width: 100,
    height: 50
};
console.log(square.getArea);

Maybe we can choose separate syntax to call those functions.

Maybe call the function like this: (since this syntax already exists)

getArea(square);
// instead of
square calls getArea();

from typescript.

knazeri avatar knazeri commented on March 28, 2024

Yea well, object literals are not the only problem:

interface A {
    default void foo() {
        console.log("A");
    }
}

interface B {
    default void foo() {
        console.log("B");
    }
}

class Bar implements A, B {
    default void foo() {    // required for conflict resolution
        console.log("Bar"); 
    }
}

var o = new Bar();
A o1 = o;
B o2 = o;
o.foo(); // Bar
o1.foo; // probably want A
o2.foo; // probably want B

From the client code perspective, default methods are just ordinary methods, so in case of the simple example with one class that implements an interface with a default method, prototype seems to be fine, however most of the time client code that invokes the default method will have to invoke the method at the call site. same old story. πŸ’€

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

Hmm... this is my current suggestion:

class Square {
  width: number;
  height: number;
}

// I think this is explicit by itself, saying "This will extend Square.prototype".
extending Square {
  get area() {
    return this.width * this.height;
  }
}

// Let's make these two extending methods coexist.
function getArea() joins Square {
  // Extension functions can only access public properties, as normal functions works so.
  return this.width * this.height;
}

var square: Square;
var x: any = square;
square.area;
square<-getArea(); // New syntax to call extension functions
getArea(square); // Still allows normal function call
x<-getArea(); // Even works without any type information

becomes

var Square = (function () {
    function Square() {
    }
    return Square;
})();

(function () {
    Object.defineProperty(Square.prototype, "area", {
        get: function () {
            return this.width * this.height;
        },
        enumerable: true,
        configurable: true
    });
});

function getArea(_this) {
  return _this.width * _this.height
}

var square;
var x = square;
square.area;
getArea(square);
getArea(square);
getArea(x);

Edited: Added getArea(square);

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

BTW, wouldn't 'conditional extension' help us? I think some extensions are to polyfill new standard functions for old browsers.

extending Window {
  // We don't want to overwrite functions that potentially already exist.
  createImageBitmap() { /* */ }
}

extending Window {
  // We cannot do this because this is already declared in lib.d.ts.
  setImmediate() { /* */ }
}

We would have to keep using Window.prototype.x = something, or window.x = something here, to add functions conditionally.

from typescript.

electricessence avatar electricessence commented on March 28, 2024

So is this simply a syntax discussion? Since this can already be done this way:

module A {
  class B {
    c():void {}
  }

 // somewhere else.
 module B {
 // Extend.
  d():void {}
 }

}

from typescript.

ivogabe avatar ivogabe commented on March 28, 2024

@electricessence That way you can add static members, not instance members (to the prototype).

@saschanaz I don't think we need a special syntax for that, because you don't need to add type-info for those functions. You can just write:

if (!window.setImmediate) {
    window.setImmediate = (cb: () => void) => window.setTimeout(cb, 1);
}

window.setImmediate(() => console.log(''));

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

@ivogabe, That's right, but I would be happier if I can do that also with extension syntax, in the future when this is implemented anyway. Similar thing, similar syntax ;)

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

@danfma, πŸ‘

from typescript.

danquirk avatar danquirk commented on March 28, 2024

If all you want to do is add an instance method why not just allow class declarations to merge and avoid the need for new syntax?

from typescript.

knazeri avatar knazeri commented on March 28, 2024

@danquirk I guess the primary use case of extension methods is to make them work on interfaces

from typescript.

jbondc avatar jbondc commented on March 28, 2024

Is this similar to a 'trait'?

class Shape {
 // ...
}

trait dimensions {
      getArea() { return /* ... */; }
}
Shape use dimensions;
Shape use {
      getArea() { return /* ... */; }
}

Or for compiled code:

class Shape2 extends Shape {
  use dimensions;
}

I don't know about 'use' as keyword, 'with' could be re-used:

Shape with dimensions;
Shape with {
      getArea() { return /* ... */; }
}

class Shape2 extends Shape {
  with dimensions;
}

from typescript.

jbondc avatar jbondc commented on March 28, 2024

Would be nice if compiled time 'traits' and runtime 'extensions' can be baked into same concept:
http://wiki.ecmascript.org/doku.php?id=strawman:trait_composition_for_classes

from typescript.

zlumer avatar zlumer commented on March 28, 2024

@electricessence @danfma the problem lies mostly with compiler output, not syntax. You can see @RyanCavanaugh's great summary of two different approaches and their pros and cons.

from typescript.

jbondc avatar jbondc commented on March 28, 2024

Personally prefer using static functions and not touching the prototype, e.g.

trait count {
    static countCalls = 0;
     count() {
        countCalls++;
        return this.length;
   }
}

Compiles to:

trait_count = {};
trait_count.countCalls = 0;
trait_count.count = function(type)
{
   return type.length;
}
class foo {
   with count;
}

Compiles to:

var foo = ...
/* begin trait_count */ 
foo.countCalls = 0;
foo.prototype.count = function() {
   foo.countCalls++;
   return trait_count.count(this);
}
/* end trait_count */ 

from typescript.

jbondc avatar jbondc commented on March 28, 2024

Actually with static modifiers, compiled version would look more like:

trait_count.count = function(static, type, ...arguments)
{
   static.countCalls++;
   return type.length;
}

var foo = ...
/* begin trait_count */ 
foo.countCalls = 0;
foo.prototype.count = function() {
   return trait_count.count(foo, this);
}
/* end trait_count */ 

A bit uglier, only if you used 'static'. In most cases, you'd extend the prototype and proxy to a static method. I use traits again since an extension really looks like an anonymous trait.

from typescript.

WanderWang avatar WanderWang commented on March 28, 2024

Highly suggested extension keyword

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

With the advent of union types, I think the "rewrite call sites" approach is effectively ruled out, as we would no way to emit a call to a method that came in via a union type where one was an extension method and one wasn't (or if they came from two different places).

I think this is thematic with #182 and other places where we want to have something declared in lib.d.ts that you can use as a base class (in an extends clause), but can also add members to via module or interface merging.

One thing we could do is to allow you to add methods to the prototype of a class. Something like this could work:

class Dog { 
 ...
}

extends class Dog {
  woof() { /* ... */ }
}
var x = new Dog();
x.woof();

Would emit:

// Begin regular class emit
var Dog = (function () {
    function Dog() { }
    return Dog;
})();
// End regular class emit

// Begin extends class emit
Dog.prototype.woof = function () {
};
// End extends class emit
var x = new Dog();
x.woof(); // Not altered

A declare extends class would work similarly.

The only restrictions here are that an extends class could not declare initialized instance members as those initializations are emitted into the top of the constructor body. Similarly, you would not be able to add constructors or construct signatures to a class. Since there's no runtime analogue to either of these, the restrictions shouldn't be too bothersome.

from typescript.

knazeri avatar knazeri commented on March 28, 2024

Although class extension is great asset to the language, I'm a bit disappointed not to be able to extend an interface in TypeScript while languages tend to evolve to streamline methods of adding new functionality to the interfaces!

from typescript.

NoelAbrahams avatar NoelAbrahams commented on March 28, 2024
extends class Dog {
  woof() { /* ... */ }
}

πŸ‘

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

I'm a bit disappointed not to be able to extend an interface in TypeScript

Can you clarify? You can extend (subclass) an interface with extends or add members to it (augment) through interface merging.

from typescript.

knazeri avatar knazeri commented on March 28, 2024

@RyanCavanaugh
My understanding of Extension Methods is how C# does it via static methods or Java with default methods. Lets be honest, the real benefit of having extension methods is with interfaces not concrete classes. Sure you can use interface merging, but then you have to rewrite the implementations of those interfaces you have implemented throughout all your libraries.

IEnumerable<T> in C# is a great example of extension methods, I guess the whole LINQ depends on it. I was thinking with ECMA6 generators, we could have a general Iterable interface containing @@iterator and extend it linq style, could be used on String, Array, Map, Set and Generator.

Now instead, in order to extend an existing interface, we have to merge it with another interface with optional methods (not to break existing code) and extend it to all the classes implementing that interface with an ugly mixin.

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

Maybe we should find more JS-like way for those "extension methods", for example, bind() or call()...

class Foo { /* */ }
function fooExtensionMethod(this: Foo) { /* */ }

var foo: Foo;
fooExtensionMethod.call(foo);
// This is not so good... "object, method" order, please do not be backward
interface Object {
  callFn(func: Function, ...args: any[]); // from an external library?
}

foo.callFn(fooExtensionMethod);
// This is not really foo.fooExtensionMethod, but isn't it just sufficient?

(Burrowing concepts from #229, maybe we also need to solve #212)

from typescript.

sccolbert avatar sccolbert commented on March 28, 2024

I'm πŸ‘Ž on extending the object prototype. An extension doesn't own the class, it shouldn't be able to mess with it's prototype. The issues @saschanaz mentions are real, and IMHO not worth the effort to attempt to surmount, because the number of corner cases in the case of dynamically loaded script is huge.

I would be πŸ‘ on rewriting the call site (just hypothetically speaking), but that would open the door to rewriting call sites in other places (like overloaded functions) which, as far as I understand, is a ship that's already sailed, and I would be πŸ‘Ž on semantics special-cased to extension methods.

So, while I like the idea of extension classes, I'm not sure there's an effective way to implement them, given the larger constraints already imposed on the language.

from typescript.

metaweta avatar metaweta commented on March 28, 2024

I agree with @sccolbert. TypeScript isn't trying to be C# or Java, it's trying to add some sanity to JavaScript. The extensions concept, while cool, is at odds with the way JavaScript classes actually work.

from typescript.

NoelAbrahams avatar NoelAbrahams commented on March 28, 2024

These two statements appear to be at odds with each other.

An extension doesn't own the class, it shouldn't be able to mess with it's prototype.

The extensions concept, while cool, is at odds with the way JavaScript classes actually work.

That's exactly how JavaScript works.

I don't really think that TypeScript should bend over backwards to cater for situations where people are happy to load dodgy scripts from third parties.

from typescript.

metaweta avatar metaweta commented on March 28, 2024

Let me rephrase: an extensions concept that requires rewriting call sites is at odds with the way javascript classes actually work, and an extensions concept that modifies the prototype is at odds with the way C#/Java programmers understand extensions.

from typescript.

zlumer avatar zlumer commented on March 28, 2024

@RyanCavanaugh this is an insanely detailed explanation, thank you! When this thread started, I was sure that extension methods are a very good idea, now I believe we're better of without them (at least for now).

from typescript.

niemyjski avatar niemyjski commented on March 28, 2024

+1

from typescript.

 avatar commented on March 28, 2024

At present, the primitive types can be extended as:

// Extending String prototype
interface String {
    format(...params: any[]): string;
}

// using `any` so consumer can pass number or string,
// might be a better way to constraint types to
// string and number only using generic?
String.prototype.format = function (...params: any[]) {
    var s = this,
        i = params.length;

    while (i--) {
        s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), params[i]);
    }

    return s;
};

// Usage:
// 'Added {1} by {0} to your collection'.format(artist, title)

Would be nice to have extension keyword fit in that kind of context as well. :)

from typescript.

roelvanlisdonk avatar roelvanlisdonk commented on March 28, 2024

+1

from typescript.

Kavignon avatar Kavignon commented on March 28, 2024

How could someone contribute for the extension classes in Typescript ?

from typescript.

bchavez avatar bchavez commented on March 28, 2024

Would be great if we could extend enums with this. Something like

enum View{
   Edit,
   Detail
}

View.Edit.asString()

vs what we have to do now:

View[View.Edit]

The first one is a bit more readable and flows nicer than the latter.

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

Note that you can add to an enum using module (or namespace):

enum E {
    A, B
}
module E {
    export function toString(e: E): string {
        return E[e];
    }
}

var x = E.toString(E.A);

from typescript.

benliddicott avatar benliddicott commented on March 28, 2024

#3508 looks like the compromise solution. Avoids backwardness, doesn't mess with prototypes and doesn't rely on type information. The only downside is it has a funny-looking syntax. I'd prefer -> for call.

So this:

o.my()::Extension.method().rocks();
// or preferably this
o.my()->Extension.method().rocks();

desugars to:

Extension.method.prototype.call(o.my()).rocks();

There's a possible ambiguity if there were extension properties though, or methods which return extension methods. There's also the issue that using a different syntax instead of dot hurts the motivating case which is I think is transparency.

from typescript.

Zorgatone avatar Zorgatone commented on March 28, 2024

πŸ‘

from typescript.

WanderWang avatar WanderWang commented on March 28, 2024

It seems implement in TypeScript 1.7

class Shape {
 // ...
}

interface Shape {
    getArea()
}


var x = new Shape();
console.log(x.getArea()); // OK

Should we close this issue ?

from typescript.

Zorgatone avatar Zorgatone commented on March 28, 2024

That does not create a new method

from typescript.

roelvanlisdonk avatar roelvanlisdonk commented on March 28, 2024

Well this is exactly what I was looking for, so for me a YES, on closing this issue.

from typescript.

WanderWang avatar WanderWang commented on March 28, 2024

@Zorgatone

you can create a new method with this way

class Shape {
 // ...
}

interface Shape {
    getArea()
}

Shape.prototype.getArea = function(){
}


var x = new Shape();
console.log(x.getArea()); // OK

from typescript.

Zorgatone avatar Zorgatone commented on March 28, 2024

Oh, that's with prototype. I wasn't considering that.

I tried to do something similar, but without the interface trick πŸ‘

from typescript.

niemyjski avatar niemyjski commented on March 28, 2024

@WanderWang @Zorgatone You can't do that, I tried doing that in separate files using the es6 module syntax and got tons of errors. I think it only works with the legacy external modules.

from typescript.

Zorgatone avatar Zorgatone commented on March 28, 2024

Uhm then keep the issue open :D

from typescript.

falsandtru avatar falsandtru commented on March 28, 2024

-1 Humans cannot understand code flow using extension methods. Extension should be declarative feature.

from typescript.

electricessence avatar electricessence commented on March 28, 2024

The problem with doing this is that (as hopefully we’ve all dealt with), is it could pollute the existing code in a more complex environment.
β€œExtensions” would simply be compiler sugar for writing utility methods that would pass the β€˜this’ along.

From: Wander Wang [mailto:[email protected]]
Sent: Friday, October 30, 2015 5:52 AM
To: Microsoft/TypeScript
Cc: electricessence
Subject: Re: [TypeScript] Suggestion: Extension methods (#9)

@Zorgatone https://github.com/Zorgatone

you can create a new method with this way

class Shape {
// ...
}

interface Shape {
getArea()
}

Shape.prototype.getArea = function(){
}

var x = new Shape();
console.log(x.getArea()); // OK

β€”
Reply to this email directly or view it on GitHub #9 (comment) .Image removed by sender.

from typescript.

falsandtru avatar falsandtru commented on March 28, 2024

I don't think that it is declarative because we cannot get the whole story. We must find pieces of a class.

from typescript.

 avatar commented on March 28, 2024

If you are building the library yourself, then you should be able to add the additional methods in the class. If you are working with someone's else library (which is most likely documented) then you can add extension methods elsewhere in your code without touching vendor/** code. Finding the pieces to form the 'whole story' is a non-issue especially when editors are there to assist you.

Splitting the code into separate files vs. dumping everything in one place, both have pros and cons. Giving this choice to the developer however they want to consume the feature (or avoid it) would be the best strategy from TypeScript team's standpoint. Any feature of any language can be misused.

from typescript.

xfoxfu avatar xfoxfu commented on March 28, 2024

πŸ‘

from typescript.

xfoxfu avatar xfoxfu commented on March 28, 2024

πŸ‘

I would suggest that Extension is only useful for telling the compiler there is an existing member for the existing class, so the code:

class A {}
extension class A {
  func() : void =>  {
  }
}

could be just compiled to:

class A{}
A.prototype.func = () : void => {
}

but such Extension is of great use to intellisense.

from typescript.

TrabacchinLuigi avatar TrabacchinLuigi commented on March 28, 2024

if this isn't already the case i think @ronzeidman is absolutely right πŸ‘

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

If we are really hesitant to support extension class Homu syntax, can we just add type information from Homu.prototype.method1 = () => {}, similar to what Salsa does (#4955)?

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

@ronzeidman @TrabacchinLuigi you need to read #9 (comment)

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

We're almost certainly not taking up any new runtime operators that don't have at least some traction with the ES committee (especially when there are competing operators with similar meaning), so taking that to ESDiscuss would be the next step if you'd like to see it happen.

from typescript.

TrabacchinLuigi avatar TrabacchinLuigi commented on March 28, 2024

@RyanCavanaugh I like the bind operator too!

from typescript.

ronzeidman avatar ronzeidman commented on March 28, 2024

@RyanCavanaugh I agree, and the bind operator (of which I guess people agree on since its an ES7 suggestion) could work really well, especially if you could configure the type of "this" the function can get like in #6018 suggestion

from typescript.

mattleibow avatar mattleibow commented on March 28, 2024

Just wanted to give my humble suggestion, pardon my two-week TypeScript skills.

I have been reading this discussion and maybe we are trying to do something primarily designed for type-based systems. JavaScript is much less typey, so work with that (this syntax is just one way):

Given this class:

class Square {
    width: number;
    height: number;
}

Case 1: Does not touch the prototype:

extension class SimpleSquareExtensions<Square> {
    depth: number = 0;
    static scale: number = 0;

    // must be static
    static function getArea(this square: Square) {
        // Can't access `depth` at all, not static
        // Can't do this: `square.scale` - not on Square
        // Can do this: `SimpleSquareExtensions.scale`
        return square.width * square.height;
    }
}

// JS:
SimpleSquareExtensions.scale = 0;
SimpleSquareExtensions.prototype.depth = 0;
SimpleSquareExtensions.getArea = function (square) {
    return square.width * square.height;
}

This is a very basic type that does not touch the Square prototype, so:

var sq: Square = new Square();

var area = sq.getArea();
// JS:
// var area = SimpleSquareExtensions.getArea(sq);

var anySq: any = sq;
var anyArea = anySq.getArea(); // No Intellisense for getArea()
// JS:
// var anyArea = anySq.getArea();
// Runtime error - expected

This works like C# dynamic object, I can't apply a Square extension method to an object that might not be a square.

Also, we could, or not, permit other non-extension members on the class, such as the depth and scale properties. (could be methods too)

Case 2: Adds to the prototype:

extension class MoreSquareExtensions<Square> {
    extension static scale: number = 0;
    extension depth: number = 0;

    extension static function getArea(this square: Square) {
        // Can't do this: `square.depth` - static method
        // Can't do this: `Square.depth` - not static field
        // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
        // Can't do this: `square.scale` - static field
        // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
        // Can do this: `Square.scale`
        return square.width * square.height;
    }

    // no need for static
    extension function getVolume(this square: Square) {   
        // Can't do this: `Square.depth` - not static field
        // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
        // Can't do this: `square.scale` - static field
        // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
        // Can do this: `square.depth`
        // Can do this: `Square.scale`             
        return square.width * square.height * square.depth;
    }
}

// JS:
Square.scale = 0;
Square.prototype.depth = 0;
Square.getArea = function (square) {
    return square.width * square.height;
}
Square.prototype.getVolume = function () {
    return this.width * this.height * this.depth;
}

Due to the extension keyword, the developer is instructing the compiler to modify the type. The dev is responsible for resolving conflicts:

var sq: Square = new Square();

var area = sq.getArea();
// JS:
// var area = Square.getArea(sq);

var anySq: any = sq;
var anyArea = anySq.getArea(); // Still no Intellisense?
// JS:
// var anySq = sq;
// var anyArea = anySq.getArea();
// works as expected, because it is on the prototype

sq.depth = 10;
var volume = sq.getVolume();
// JS:
// sq.depth = 10;
// var volume = sq.getVolume();

Basically, what I am trying to do is sort of follow what the dynamic keyword would do in C#, but also permit the explicit extension of an existing prototype like JavaScript.

I am still not too sure what the best is for the class declaration:

extension class MyExtensions<Square> { }
class MyExtensions extensionof Square { }
extension class MyExtensions extension Square { }

might be best to avoid the instantiation of MyExtensions... C# required that the class and method be static, and the method have the this. But, JavaScript does allow the extension if instance members, so static can't be a requirement. Maybe we could say that if the method was explicitly marked as extension, then the first this parameter can be removed and you can use the this keyword in the method body itself. This way the extension fields and existing fields can work the same?

from typescript.

hdachev avatar hdachev commented on March 28, 2024

Case 1 would be the best thing since sliced bread because it'll work with
interfaces, generics, builtins and third party types extremely well. It's
an extremely modular feature, and will work automatically with future
extensions of the type system.

Case 2 only works for extending classes, can't work on interfaces, can't
work on builtins without polluting them, and can't work only on
select generic type parametrizations.

For example - in Case 1 - when imported, your getArea method will be
available on an HTMLCanvas element, because it has width and height
properties. How awesome is that?

On Saturday, 11 June 2016, Matthew Leibowitz [email protected]
wrote:

Just wanted to give my humble suggestion, pardon my two-week TypeScript
skills.

I have been reading this discussion and maybe we are trying to do
something primarily designed for type-based systems.
JavaScript is much less typey, so work with that (this syntax is just one
way):

Given this class:

class Square {
width: number;
height: number;
}

Case 1: Does not touch the prototype:

extension class SimpleSquareExtensions {
depth: number = 0;
static scale: number = 0;

// must be static
static function getArea(this square: Square) {
    // Can't access `depth` at all, not static
    // Can't do this: `square.scale` - not on Square
    // Can do this: `SimpleSquareExtensions.scale`
    return square.width * square.height;
}

}

// JS:
SimpleSquareExtensions.scale = 0;
SimpleSquareExtensions.prototype.depth = 0;
SimpleSquareExtensions.getArea = function (square) {
return square.width * square.height;
}

This is a very basic type that does not touch the Square prototype, so:

var sq: Square = new Square();

var area = sq.getArea();
// JS:
// var area = SimpleSquareExtensions.getArea(sq);

var anySq: any = sq;
var anyArea = anySq.getArea(); // No Intellisense for getArea()
// JS:
// var anyArea = anySq.getArea();
// Runtime error - expected

This works like C# dynamic object, I can't apply a Square extension
method to an object that might not be a square.

Also, we could, or not, permit other non-extension members on the class,
such as
the depth and scale properties. (could be methods too)

Case 2: Adds to the prototype:

extension class MoreSquareExtensions {
extension static scale: number = 0;
extension depth: number = 0;

extension static function getArea(this square: Square) {
    // Can't do this: `square.depth` - static method
    // Can't do this: `Square.depth` - not static field
    // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
    // Can't do this: `square.scale` - static field
    // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
    // Can do this: `Square.scale`
    return square.width * square.height;
}

// no need for static
extension function getVolume(this square: Square) {
    // Can't do this: `Square.depth` - not static field
    // Can't do this: `MoreSquareExtensions.depth` - not on MoreSquareExtensions
    // Can't do this: `square.scale` - static field
    // Can't do this: `MoreSquareExtensions.scale` - not on MoreSquareExtensions
    // Can do this: `square.depth`
    // Can do this: `Square.scale`
    return square.width * square.height * square.depth;
}

}

// JS:
Square.scale = 0;
Square.prototype.depth = 0;
Square.getArea = function (square) {
return square.width * square.height;
}
Square.prototype.getVolume = function () {
return this.width * this.height * this.depth;
}

Due to the extension keyword, the developer is instructing the compiler to
modify the type. The dev is responsible for resolving conflicts:

var sq: Square = new Square();

var area = sq.getArea();
// JS:
// var area = Square.getArea(sq);

var anySq: any = sq;
var anyArea = anySq.getArea(); // Still no Intellisense?
// JS:
// var anySq = sq;
// var anyArea = anySq.getArea();
// works as expected, because it is on the prototype

sq.depth = 10;
var volume = sq.getVolume();
// JS:
// sq.depth = 10;
// var volume = sq.getVolume();

β€”
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#9 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AASm69pd5PdoRiuIuBf1_-E7L560SIVDks5qKwScgaJpZM4CNZ1k
.

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on March 28, 2024

@mattleibow seems like you're proposing partial classes, see #563

from typescript.

frogcjn avatar frogcjn commented on March 28, 2024

can we extension interface? interface can also have some implemented functions, just like Swift:

interface Rect {
    x: number
    y: number
}

extension Rect {
    area() => this.x * this.y
}

Swift version:

protocol Rect {
    var x: Float {get}
    var y: Float {get}
}
extension Rect {
    func area() -> Float {
        return self.x * self.y
    }
}

from typescript.

saschanaz avatar saschanaz commented on March 28, 2024

can we extension interface?

It can be dangerous: #9 (comment), #9 (comment)

from typescript.

frogcjn avatar frogcjn commented on March 28, 2024

@saschanaz I don't think so. Swift already deal with it.
It just add a prototype function to every class which implements it.

There is many detail works done by Swift team in Swift to solve the worry about the different package issue.

from typescript.

frogcjn avatar frogcjn commented on March 28, 2024

@saschanaz I think interface extension is no more dangerous than class extension.

The way to solve the conflicting problem is using developerβ€˜s import to decides which implementation should use. And also when package author extends some global interface or properties, they always use perfix to identify the extension. For example, RxSwift package extends UIView class with rx_dellocated. When developer import "RxSwift", the UIView will have rx_deallocated property. If developer don't import "RxSwift", there is no rx_deallocated with UIView.

from typescript.

alonbardavid avatar alonbardavid commented on March 28, 2024

I'm not sure if this has been suggested yet (this is a long thread), but what about compiler warning or compiler errors with explicit override (like eslint "ignore this" comments) for call site replacement?

That is in the situation implied in this comment , instead of silently failing in the second example an error is emitted akin to "extension method called on unknown type" and the following code can be used to remove the error:

process(((n) => {
    n.getArea(); //@compiler:no extension
}));

I haven't seen this type of explicit compiler hints in typescript, and perhaps it's intentional, but I think that call site replacement is a very powerful tool that a majority of developers would love to use, and the situation described in the comment will almost never happen.
I don't think Typescript should fall into undefined behaviour at runtime, but I think that an error and an explicit optout will prevent anyone from unwittingly making this mistake.

from typescript.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.