Proposal for php7 class method return

php.internals

Daniel Ciochiu

10 years ago
Hi, I have a proposal that for a given method with a return type of <self>, if method does not return anything than it should it's instance. It will reduce code by only one line, but will improve consecutive method calls. class myClass { protected $value; function setValue($value) : self { $this->value = $value; } function doubleValue() : self { $this->value*=2; } function getValue() : self { return $this->value; } } $calculator = new myClass(); $returnValue = $calculator->setValue(3) ->doubleValue() ->getValue(); echo $returnValue; Any return defined in the methods body would override the return of the class instance. I am looking forward to your reaction. Thank you! DanielCiochiu

Marco Pivetta

10 years ago
Hey Daniel, I've been playing around with `self` for a while (mostly dealing with code-generators and inheritance), and what I found is that it doesn't really make sense as a type-hint, not even in interfaces. Besides the type-work to be done to do `function setValue($value) : myClass`, what is the advantage of having `self`? Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/ On 9 July 2016 at 08:47, Daniel Ciochiu <daniel@ciochiu.ro> wrote:

Rasmus Schultz

10 years ago
Just my opinion, but I view method chaining as a poor man's work-around to a missing (and much more versatile) language feature, and would therefore rather not see us adding a feature that inspires what is, in my opinion, and anti-pattern. Functions are intended as a means of returning the result of an operation - when you return the object itself, that's not the result of an operation, so this is highly non-idiomatic use of a very fundamental language feature, and it clouds the role of functions and return-values. What you're really trying to accomplish is something like the ".." operator found in some other languages - this is known as the "cascade operator" in Dart, for example: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html There are several problems with method-chaining - the biggest problem, as demonstrated by the cascade operator, is that this doesn't need to be a problem you solve for every class; it can be done systemically with a cascade operator, e.g. supported for every class, not just those where you weave in this requirement. Also, method chaining is inferior to a cascade operator, which would work not only for methods, but for properties as well. More over, a cascade operator would work for all existing libraries, whether the designer thought about cascading use of various features or not. In short, this shouldn't and doesn't need to be a problem every developer solves for each and every library - we can solve it for the language instead. In other words, it seems you're trying to address a problem that shouldn't exist in the first place, so that you can solve a recurring problem you should not have to solve at all. I would be much more interested in seeing a proposal for a cascade operator, which, in my opinion, addresses the real problem currently driving developers to use method-chaining - I can only view that as an unfortunate work-around. In my opinion, a cascade operator would move the language forward, while the addition of a magical type-hint acting as sugar for "return $this" merely solves a superficial problem - and propagates a bad practice. On Sat, Jul 9, 2016 at 8:47 AM, Daniel Ciochiu <daniel@ciochiu.ro> wrote:

Jesse Schalken

10 years ago
On Sun, Jul 10, 2016 at 8:34 PM, Rasmus Schultz <rasmus@mindplay.dk> wrote:
> > What you're really trying to accomplish is something like the ".." > operator found in some other languages - this is known as the "cascade > operator" in Dart, for example: > > > http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html > >
While I was writing about an operator to set object properties in-line here: http://news.php.net/php.internals/93662 , I considered it would be easy to add method calls to the same syntax: return Blah::create(5) { prop1 = 'hello', prop2 = a_func(), setThing(9), setOtherThing(1), }; which is in principle just an alternative syntax to as the cascade operator in Dart. I agree entirely that method chaining is just a verbose workaround for the lack of such a language feature. I've found a few problems with method chaining which this would resolve: 1. The verbosity of adding "return $this;" to all your mutator methods (Daniel Ciochiu's issue). 2. You can't return a value from a mutator method while also supporting method chaining. In theory mutator methods shouldn't need a return value (command-query separation) but in many cases they do, such as a setter which returns the previous value, a pop() method on a stack, or a delete($where) method returning the number of deleted records. 3. It is often unclear whether a method being chained is returning the same object it was called on, a copy of the object, or an instance of a different class altogether. You can work around this with careful method naming and documentation ("with.." for new instance, "set.." for same instance etc), but language-level support eliminates this issue entirely. You could really solve a whole range of problems with such a feature.

Marco Pivetta

10 years ago
Hi Jesse, On 10 July 2016 at 14:31, Jesse Schalken <me@jesseschalken.com> wrote:
> On Sun, Jul 10, 2016 at 8:34 PM, Rasmus Schultz <rasmus@mindplay.dk> > wrote: > > > > > What you're really trying to accomplish is something like the ".." > > operator found in some other languages - this is known as the "cascade > > operator" in Dart, for example: > > > > > > > http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html > > > > > > While I was writing about an operator to set object properties in-line > here: http://news.php.net/php.internals/93662 , I considered it would be > easy to add method calls to the same syntax: > > > return Blah::create(5) { > prop1 = 'hello', > prop2 = a_func(), > setThing(9), > setOtherThing(1), > }; > > > which is in principle just an alternative syntax to as the cascade operator > in Dart. > > I agree entirely that method chaining is just a verbose workaround for the > lack of such a language feature. I've found a few problems with method > chaining which this would resolve: > > 1. The verbosity of adding "return $this;" to all your mutator methods > (Daniel Ciochiu's issue). >
`return $this;` is a fairly common anti-pattern. It can be useful in few edge-cases, like immutable APIs (CoW) and DSL builders, but it's usually a bad idea. I ranted extensively about this at https://ocramius.github.io/blog/fluent-interfaces-are-evil/
> 2. You can't return a value from a mutator method while also supporting > method chaining. In theory mutator methods shouldn't need a return value > (command-query separation) but in many cases they do, such as a setter > which returns the previous value, a pop() method on a stack, or a > delete($where) method returning the number of deleted records. > 3. It is often unclear whether a method being chained is returning the > same object it was called on, a copy of the object, or an instance of a > different class altogether. You can work around this with careful method > naming and documentation ("with.." for new instance, "set.." for same > instance etc), but language-level support eliminates this issue > entirely. >
Are you actually talking about a `$this` return-hint? That would indeed solve some issues, by enforcing identity as part of the return type. Don't think that there are scenarios where identity is required though, as it would be useless (the hint would not serve any actual behavioral/expressiveness improvement). @Daniel: the fact that an implicit return is added to the opcodes for a method that has a `self` hint seems useless/magic to me. It would basically allow automatic fluent interfaces, enforcing something useless though, since a fluent interface does not provide any actual usefulness per-se (besides code-style preferences). Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/

Jesse Schalken

10 years ago
On Mon, Jul 11, 2016 at 10:18 PM, Marco Pivetta <ocramius@gmail.com> wrote:
> Hi Jesse, > > `return $this;` is a fairly common anti-pattern. It can be useful in few > edge-cases, like immutable APIs (CoW) and DSL builders, but it's usually a > bad idea. I ranted extensively about this at > https://ocramius.github.io/blog/fluent-interfaces-are-evil/ >
I agree, but nonetheless at the cost of the problems both you (in your blog post) and I have mentioned, "return $this;" does save the verbosity of repeating a variable name, and often saves a variable altogether: $blah = new Blah(); $blah->setFoo(1); $blah->setBaz(2); $this->setBlah($blah); becomes $this->setBlah((new Blah()) ->setFoo(1) ->setBaz(2) ); Some cascade operator or inline group method call/property set achieves the same thing, but without all the problems of "return $this;": $this->setBlah((new Blah()) ->>setFoo(1) ->>setBaz(2) ); $this->setBlah(new Blah() { setFoo(1), setBaz(2), }); $this->setBlah(new Blah() { foo = 1, baz = 2, });

Marco Pivetta

10 years ago
On 11 July 2016 at 21:51, Jesse Schalken <me@jesseschalken.com> wrote:
> > does save the verbosity of repeating a variable name, and often saves a > variable altogether: > > $blah = new Blah(); > $blah->setFoo(1); > $blah->setBaz(2); > $this->setBlah($blah); > > > becomes > > $this->setBlah((new Blah()) > ->setFoo(1) > ->setBaz(2) > ); > >
Right: to save one variable assignment you now have 3 function calls. I think I see your problem (if this is about performance) :-P Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/

Rowan Collins

10 years ago
On 11/07/2016 20:56, Marco Pivetta wrote:
> On 11 July 2016 at 21:51, Jesse Schalken <me@jesseschalken.com> wrote: > >> does save the verbosity of repeating a variable name, and often saves a >> variable altogether: >> >> $blah = new Blah(); >> $blah->setFoo(1); >> $blah->setBaz(2); >> $this->setBlah($blah); >> >> >> becomes >> >> $this->setBlah((new Blah()) >> ->setFoo(1) >> ->setBaz(2) >> ); >> >> > Right: to save one variable assignment you now have 3 function calls. I > think I see your problem (if this is about performance) :-P
Huh? There's exactly the same number of function calls. And who said this was ever about performance? Again, it's just different syntax for doing things we can already do. Even the object cascade operator is just hiding the assignment to a temporary variable - think of it like post-increment: $bar = $foo; $foo = $foo + 1; $bar = $foo++; $bar = $foo; $foo->inc(); $bar = $foo->>inc(); (I'm not suggesting this is a literal use case for the operator, just highlighting the parallels; please don't start picking at it.) Regards,
-- Rowan Collins [IMSoP]

Marco Pivetta

10 years ago
On 11 July 2016 at 22:15, Rowan Collins <rowan.collins@gmail.com> wrote:
> Again, it's just different syntax for doing things we can already do. > >
Spot on! Plus it's something arguably considered a bad idea. I don't think that adding language features for this sort of thing is useful. Maybe it's even just harmful. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/

Rowan Collins

10 years ago
On 11/07/2016 21:17, Marco Pivetta wrote:
> On 11 July 2016 at 22:15, Rowan Collins <rowan.collins@gmail.com > <mailto:rowan.collins@gmail.com>> wrote: > > Again, it's just different syntax for doing things we can already do. > > > Spot on! Plus it's something arguably considered a bad idea. I don't > think that adding language features for this sort of thing is useful. > Maybe it's even just harmful.
Looking through the reasons given in your blog post, most of them don't really apply with a dedicated operator, because they're about the *contract*, and a cascade operator is only useful when the contract is to return something *other* than the current object. Specifically, it relies on the assumption that the method is not returning a new instance. To adapt one of your examples: $counter->count(); $counter->count(); $counter->count(); $counter->count(); echo $counter->getCount(); // This relies on count() mutating the current object, probably the contract is to return void The same thing, with ->> as a cascade operator: echo $counter ->>count() ->>count() ->>count() ->>count() ->getCount(); Or perhaps for clarity, pull the echo separately: $counter ->>count() ->>count() ->>count() ->>count(); echo $counter->getCount(); The cascade operator is a tool to use *instead of* a fluent interface, not *with* it, and it solves a lot of the problems you outlined - $counter here can be mocked, wrapped, substituted for different implementations, etc, because the ->> operator makes no more assumptions than the code was already making. Regards,
-- Rowan Collins [IMSoP]

Stas Malyshev

10 years ago
Hi!
> $counter->count(); > $counter->count(); > $counter->count(); > $counter->count(); > echo $counter->getCount();
I still fail to see what's so wrong in this code that it needs a whole new operator to fix it.
-- Stas Malyshev smalyshev@gmail.com