Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic this types #36

Closed
wants to merge 5 commits into from
Closed

Conversation

matulkum
Copy link

@matulkum matulkum commented Nov 14, 2017

@nadako
Copy link
Member

nadako commented Nov 14, 2017

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

@EricBishton
Copy link
Member

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
Copy link
Author

matulkum commented Nov 14, 2017

@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.

Copy link
Author

@matulkum matulkum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed 2 little bugs in the code examples.

@markknol
Copy link
Member

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
Copy link
Author

@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
Copy link
Member

markknol commented Nov 15, 2017

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
Copy link
Author

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

@EricBishton
Copy link
Member

@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
Copy link
Member

andyli commented Nov 16, 2017

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

@RealyUniqueName
Copy link
Member

RealyUniqueName commented Nov 23, 2017

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:

  • Reduce amount of work on renaming types
  • Simplify macros which need to operate with current type
  • Simplify copy-pasting (bad side effect :) )

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

@RealyUniqueName
Copy link
Member

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
Copy link

fullofcaffeine commented Dec 5, 2018

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
Copy link
Member

markknol commented Dec 6, 2018

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

@Simn Simn mentioned this pull request Apr 6, 2019
@Gama11 Gama11 changed the title add proposal "0000-polymorphic-this-types" Polymorphic this types Apr 6, 2019
@Simn
Copy link
Member

Simn commented Apr 6, 2019

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

@kevinresol
Copy link

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
Copy link
Member

nadako commented Sep 25, 2019

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
Copy link
Member

Simn commented Jun 19, 2020

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.

@Simn Simn mentioned this pull request Jul 18, 2020
@kevinresol
Copy link

kevinresol commented Mar 9, 2021

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

  1. Unification of This type
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 Simn added this to the 2021-12 milestone Nov 8, 2021
@Simn
Copy link
Member

Simn commented Nov 8, 2021

lean-reject: too many unanswered questions

@Simn
Copy link
Member

Simn commented Nov 9, 2021

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.

@Simn Simn closed this Nov 9, 2021
@Simn Simn added rejected and removed lean-reject labels Nov 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants