Thoughts on method chains returning 'this'

In the section “Polymorphic ‘this’ type” of the Evolution Meeting 2020 blog post [Haxe Evolution meeting 2020 - Haxe - The Cross-platform Toolkit], examples are given where inheritance breaks certain method chains. Such as,

class Base {
  public function doX():Base { ... return this; }
  public function doY():Base { ... return this; }
}
class Child extends Base {
  public function doZ():Child { ... return this; }
}
new Child()
  .doZ()
  .doX() // <- this method returns a Base, not a Child!
  .doZ() // <- type error here: Base has no method doZ
  .doZ();

I program a lot with such chains, but I have always felt like they are a bit of a hack for the sake of convenience and readability. A hack that may break and requires writing a non-negligible amount of boilerplate code.

Doing X, Y, or Z has no reason to output ‘this’, except for the chaining.

And, you might want to chain some actions that already have an output that isn’t ‘this’. For example,

class Stack {
    public function pop():NotStack {...}
}
stack.pop().pop();  // Error. Pop does not return a Stack.

It seems that a proper solution would include some additional syntax at the calling site, to indicate that we want to chain operations on the same object. For example (with a few potential syntaxes),

stack~pop1()~pop2();
stack..pop1()..pop2();
stack->pop1()->pop2();

These are examples of a chaining syntax. stack is at the beginning of the chain, and pop2 is at the end of the chain. So, all method calls operate on stack, and the value of the chain is the return value of pop2.

This syntax addition would allow chaining to be used with any method without writing the ‘return this’ boilerplate. It would work with inheritance without a polymorphic ‘this’. And, it would make the intent of operating on the same object clearer.

I am not aware of any other language with this feature. I’m not sure why.

Maybe someone with more experience with programming languages and compilers has thoughts on this suggestion? The only downsides I can think of are adding more syntax to learn and needing to ensure the syntax does not conflict with existing syntax.

The only downsides I can think of are adding more syntax to learn and needing to ensure the syntax does not conflict with existing syntax.

That’s a big one though! The overall direction of Haxe seems to be to not introduce syntax too lightly, in order to remain approachable for new users. You might find that the points raised in the various inline markup discussions are similar to what you are asking.

Method chaining is an idiom that is common in some languages (JavaScript in particular), less so in others. It is a tool that may have its uses, particularly for something like the Builder design pattern. I personally don’t use the approach too often, as it seems to me to not be worth the effort to just save a couple of strokes. It is useful for Haxe’s type system to be able to model what happens in its target languages via externs. On the other hand, dedicating entirely new syntax (it’s not even a binary operator) to just this function seems like overkill.

Polymorphic this, on the other hand, would be a solution that has wider applicability (object copy, signals, as mentioned in the post) without drastically changing the language.

What you are suggesting is also possible to implement with macros (albeit without a custom operator), as is usually the case!

I am pretty sure there is a macro somewhere that implements something like:

with(object, {
  methodA();
  methodB();
  methodC();
});

I can’t find it right away, but it sounds quite straightforward to implement :slight_smile:

I am pretty sure there is a macro somewhere that implements something like:

with(object, {
  methodA();
  methodB();
  methodC();
});

I can’t find it right away, but it sounds quite straightforward to implement :slight_smile:

I came here looking for this exact thing.

Unless you have an immutable API, which some of us value because not everybody needs the most performance and prefer more safety and ergonomics