HaxeFoundation / haxe-evolution

Repository for maintaining proposal for changes to the Haxe programming language
111 stars 58 forks source link

Polymorphic `this` types #36

Closed matulkum closed 2 years ago

matulkum commented 6 years ago

Rendered version

nadako commented 6 years ago

I added a link to the rendered version so one can easily view the proposal markdown document.

EricBishton commented 6 years ago

As a first thought, isn't this what static extensions are supposed to solve? Why would that mechanism be insufficient? (https://try.haxe.org/#01b82)

using Test.Calculator;

class BasicCalculator {
    public var result = 0.0;

    public function new () {}

    public function add(a: Int): BasicCalculator {
        result += a;
        return this;
    }
}

class Calculator {
    public static function sin(c:BasicCalculator): BasicCalculator {
        c.result = Math.sin(c.result);
        return c; 
    }
}

class Test {
    static function main() {
        var calculator = new BasicCalculator();
        var result = calculator.add(2).add(3).sin().result; // <-- Totally possible in current Haxe
    }
}
matulkum commented 6 years ago

@EricBishton I agree that you solved this specific example with a static extensions. In general it is not the same though: you cannot override a method in BasicCalculator. You can also just add methods, not properties. You could not extend BasicCalculator with a static extension like in this example:

enum Mode {
   RAD;
   DEG;
}
class Calculator extends BasicCalculator {
    public var mode: Mode; 

    public function sin() {
        if( this.mode == RAD)
        this.result = Math.sin(this.result);
        else
            this.result = Math.sin(this.result/180*Math.PI);
        return this;       
    }
}

class Test {
    static function main() {
        var calculator = new Calculator();
        calculator.mode = DEG;
        var result = calculator.add(2).add(3).sin().result;
    }
}

Also static Extensions do not help in creating Extern definitions.

markknol commented 6 years ago

I wonder if this should be a type inference feature, if you leave out the return type definition, it works? Then this stays the same in its context, just as we have now.

matulkum commented 6 years ago

@markknol Actually we do NOT have that now. Currently the return type BasicCalculator.add() will infer to BasicCalculator always, even if it is called from Calculator.. and I think rightly so.

By providing this as an option to implicitly define the return type you can have it both ways and you have actual control. See also @nadako comment at the community forum: http://community.haxe.org/t/feature-proposal-polymorphic-this-types/174/5

markknol commented 6 years ago

Just want to mention that @andyli once wrote this very nice article: https://blog.onthewings.net/2010/12/19/method-chaining-and-fluent-interface-in-haxe/

But I'd love to have polymorphic this type too, I use a lot of chaining too.

matulkum commented 6 years ago

@markknol thanks for the link. Did not even see that before. So this is actually a pretty "ok" workaround for this case.

EricBishton commented 6 years ago

@markknol @andyli Perhaps, if Andy gives permission, a version of his article could make it into the Haxe cookbook on the web site (with appropriate attribution).

andyli commented 6 years ago

Feel free to take it to the cookbook. I'm glad that little post is still useful. :)

RealyUniqueName commented 6 years ago

I'd like to also be able to do this:

class Some {
  static function doSomething():this {
    var inst = new this();
    return inst;
  }

  static function doAnotherThing():Class<this> {
    return this;
  }
}

Which will allow to:

Maybe make it a reserved class name This instead of this.

RealyUniqueName commented 5 years ago

We discussed this proposal and came to a conclusion it could be useful. However, @Simn has no clear vision how to implement it and needs to do a research first. As for my previous comment about using This as the type expression, it should be a separate proposal.

fullofcaffeine commented 5 years ago

Just want to mention that @andyli once wrote this very nice article: https://blog.onthewings.net/2010/12/19/method-chaining-and-fluent-interface-in-haxe/

But I'd love to have polymorphic this type too, I use a lot of chaining too.

The link is dead. Did anyone ever add this to the cookbook? I couldn't find anything related.

markknol commented 5 years ago

@fullofcaffeine the site is up again, please PR it for the cookbook 🥇

Simn commented 5 years ago

What happens if the "this" class has type parameters?

kevinresol commented 5 years ago

I suppose it would always be the same as the "context"?

$type(foo) == $type(foo.getThis()); // for all concrete T

class Base {
  function getThis():this;
}

class Foo<T> extends Base {}
nadako commented 4 years ago

I don't like method chaining, so I didn't care much about this proposal, but I just stumbled upon a piece of code where polymorphic This type could help. Just leaving it here for consideration and test case if this is going to be implemented:

class BaseButton {
  function setClickCallback(cb:This->Void);
}

class TabButton extends BaseButton {
  function someExtraApi();
}

// and then

var button = new TabButton();
button.setClickCallback(onTabClicked);

function onTabClicked(tab:TabButton) {
  tab.someExtraApi();
}
Simn commented 4 years ago

We didn't reach a consensus on this one in our haxe-evolution meeting yesterday.

There's agreement that this would be useful, but how to actually implement it remains unclear. I don't want to accept a proposal if I don't know to actually implement it, so for the time being this will remain open.

kevinresol commented 3 years ago

I also find this useful, but I can see a few unresolved questions:

  1. Variance
    
    // currently this doesn't work because function arguments are contravariant
    interface Base {
    function set(v:Base):Void;
    }
    interface Derived extends Base {
    function set(v:Derived):Void;
    }

// Does that mean the compiler will reject the following? interface Base { function set(v:This):Void; }


Possible solution: only allow `This` type in places where covariance is expected

2. Unification of `This` type

```haxe
class Base {
    public function get():This {
        return new Base(); // is this valid? If yes, it will break in derived classes, and they must override this method. Can the compiler detect this though?
    }
}
class Derived extends Base {
    public var derived:Int;
}

new Derived().get().derived; // runtime error: instance of Base has no field derived

Possible solution: only EConst(CIdent('this')) can ever unify with This type

  1. Implementation of interfaces
interface Base {
    function get():This;
}

class BaseClass implements Base {
    public function get():BaseClass; // is this implementation valid? and how about derived classes/interfaces again?
}

Possible solution: Implementations must exactly match This types but not the "realized" types

Simn commented 2 years ago

lean-reject: too many unanswered questions

Simn commented 2 years ago

We have rejected the proposal in the haxe-evolution meeting today.

There have been long-standing open questions that have not been answered, which we interpret as the feature potentially being "nice to have", but not important enough to the general interest. Importantly, we do not reject the feature of a this-type per-se, but rather the proposal itself on these grounds. A new proposal that aims to answer open questions could lead to further discussions, and possibly acceptance.