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
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
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
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.
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
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:
- The verbosity of adding "return $this;" to all your mutator methods
(Daniel Ciochiu's issue). - 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. - 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.
Hi Jesse,
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:
- 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/
- 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.- 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
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,
});
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
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]
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
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]
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