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.
This is a fascinating concept. There's a small gap to this implementation
which I think can be improved on, but still fascinating.
So we currently have -> for invocation of a method on an object.
$a->setSomething('here')->setSomethingElse('there');
But for that to currently work setSomething must return $this. A cascade
operator would avoid this. What about => ?
$a->setSomething('here')=>setSomethingElse('there');
This alone is pretty cool, but what if you have a method that returns
something you want to use as argument? The old prototype.js library.
Consider the following
class A {
public function add( $a, $b) {
return $a + b;
}
public function subtract( $a, $b) {
return $a - $b;
}
}
For this sort of chaining I'd propose, in addition to the cascade operator,
the chain operator. This would work like the cascade operator except that
the return of the function is implicitly the first parameter of the next
argument in the chain. Something like this?
$a->add(1, 2):>subtract(3):>add(4); // return 4.
As can be seen above, I'm unsure of what the static method syntax should be.
But for that to currently work setSomething must return $this. A cascade
operator would avoid this. What about => ?$a->setSomething('here')=>setSomethingElse('there');
This will be ambiguous with array syntax, e.g.
$foo = [ $a->foo() => bar() ]
could be a call to $a->foo() for the key and to bar() for the value, or
just a chain ending with the array value $a->bar().
Naming things is hard. Inventing operators is harder. ;)
Also note that it's the first call that needs a special operator, in
order to discard the normal return and substitute the left-hand side,
otherwise mixed chains become ambiguous and probably horrible to
implement in the engine:
$foo->bar()->baz()=>bob() // what is $this inside bob() ?
For this sort of chaining I'd propose, in addition to the cascade operator,
the chain operator. This would work like the cascade operator except that
the return of the function is implicitly the first parameter of the next
argument in the chain. Something like this?$a->add(1, 2):>subtract(3):>add(4); // return 4.
This exact feature was proposed a couple of months ago by Sara Golemon.
See https://wiki.php.net/rfc/pipe-operator and the accompanying
discussion http://marc.info/?t=146196088900001&r=4&w=2
As far as I know, it's not been formally withdrawn, so might be
resurrected in some for for 7.2. It might be interesting to see a
cascade operator at the same time, but some of the same concerns that
came up in that discussion will probably apply to any proposal.
Regards,
Rowan Collins
[IMSoP]
Still interested: what's the actual added value of this sort of operator?
Marco Pivetta
But for that to currently work setSomething must return $this. A cascade
operator would avoid this. What about => ?$a->setSomething('here')=>setSomethingElse('there');
This will be ambiguous with array syntax, e.g.
$foo = [ $a->foo() => bar() ]
could be a call to $a->foo() for the key and to bar() for the value, or
just a chain ending with the array value $a->bar().Naming things is hard. Inventing operators is harder. ;)
Also note that it's the first call that needs a special operator, in
order to discard the normal return and substitute the left-hand side,
otherwise mixed chains become ambiguous and probably horrible to implement
in the engine:$foo->bar()->baz()=>bob() // what is $this inside bob() ?
For this sort of chaining I'd propose, in addition to the cascade operator,
the chain operator. This would work like the cascade operator except that
the return of the function is implicitly the first parameter of the next
argument in the chain. Something like this?$a->add(1, 2):>subtract(3):>add(4); // return 4.
This exact feature was proposed a couple of months ago by Sara Golemon.
See https://wiki.php.net/rfc/pipe-operator and the accompanying
discussion http://marc.info/?t=146196088900001&r=4&w=2As far as I know, it's not been formally withdrawn, so might be
resurrected in some for for 7.2. It might be interesting to see a cascade
operator at the same time, but some of the same concerns that came up in
that discussion will probably apply to any proposal.Regards,
Rowan Collins
[IMSoP]
Still interested: what's the actual added value of this sort of operator?
Arguably, the same value there is any syntax.
You could say that the -> operator is just a more readable form of
overloaded function calls:
class Something { function bar(int $value); }
$foo->bar(42);
vs
function bar(@overload Something $this, int $value);
bar($foo, 42);
And, indeed, that all operators are just a more readable form of
function calls.
Chaining syntax makes a certain style of programming easier. It doesn't
allow you to do anything that you can't do without it, and the change is
mostly cosmetic. Whether that's a good thing or bad thing depends
whether you think that's a good style or not.
A bit of a non-answer, perhaps, but the question is pretty woolly too.
Regards,
Rowan Collins
[IMSoP]
what's the actual added value of this sort of operator?
The only direct value to the language is brevity - mere convenience.
But I think the biggest indirectly added value, is that this will
discourage people from the chainable methods anti-pattern - so this
has value to the community; those who write chainable methods can
stop, and those who want chainable methods can get a wider choice of
libraries that were previously too verbose in use for their taste.
For this sort of chaining I'd propose, in addition to the cascade operator,
the chain operator. This would work like the cascade operator except that
the return of the function is implicitly the first parameter of the next
argument in the chain
I don't like this idea, at all - I think we should aim for something
consistent with other languages. Per the Dart spec:
A cascaded method invocation expression of the form e..suffix is equivalent to the expression (t){t.suffix; return t;}(e).
In other words, the expression always evaluates as the object, which
is fine for most cases, e.g. for everything you're doing with
chainable methods today - which can't actually return any values,
since they return $this. Note that something like PSR-7's HTTP
immutable models are actually factory methods, e.g. use-cases not
applicable to the cascade operator.
The other marginal use case perhaps is cases where a builder of some
sort ends with a call to a factory method - this can be done without
adding a second operator, by using parens in a similar fashion to Dart
and others, e.g.:
$object = ($builder
->>setFoo(1)
->>setBar(2)
->>setBaz(3)
)->build();
Or obviously:
$builder
->>setFoo(1)
->>setBar(2)
->>setBaz(3);
$object = $builder->build();
Regarding syntax - I feel the natural choice, e.g. similar to "." vs
"..", would be a longer arrow --> but that's ambiguous. (decrement
operator, greater than operator)
The proposed |> operator looks horrible and is very awkward to type,
at least on both American and Danish keyboard - I use both. (it reads
like "or greater than" in my mind, so much so that glancing over
Sara's proposal earlier, I didn't even understand what it was.)
Would something like ->> be ambiguous as well? That's fairly close too
- a double-headed arrow, not unlike the double dots of other
languages...
Still interested: what's the actual added value of this sort of operator?
Marco Pivetta
But for that to currently work setSomething must return $this. A cascade
operator would avoid this. What about => ?$a->setSomething('here')=>setSomethingElse('there');
This will be ambiguous with array syntax, e.g.
$foo = [ $a->foo() => bar() ]
could be a call to $a->foo() for the key and to bar() for the value, or
just a chain ending with the array value $a->bar().Naming things is hard. Inventing operators is harder. ;)
Also note that it's the first call that needs a special operator, in
order to discard the normal return and substitute the left-hand side,
otherwise mixed chains become ambiguous and probably horrible to implement
in the engine:$foo->bar()->baz()=>bob() // what is $this inside bob() ?
For this sort of chaining I'd propose, in addition to the cascade operator,
the chain operator. This would work like the cascade operator except that
the return of the function is implicitly the first parameter of the next
argument in the chain. Something like this?$a->add(1, 2):>subtract(3):>add(4); // return 4.
This exact feature was proposed a couple of months ago by Sara Golemon.
See https://wiki.php.net/rfc/pipe-operator and the accompanying
discussion http://marc.info/?t=146196088900001&r=4&w=2As far as I know, it's not been formally withdrawn, so might be
resurrected in some for for 7.2. It might be interesting to see a cascade
operator at the same time, but some of the same concerns that came up in
that discussion will probably apply to any proposal.Regards,
Rowan Collins
[IMSoP]
11 lip 2016 18:31 "Rasmus Schultz" rasmus@mindplay.dk napisał(a):
what's the actual added value of this sort of operator?
The only direct value to the language is brevity - mere convenience.
But I think the biggest indirectly added value, is that this will
discourage people from the chainable methods anti-pattern - so this
has value to the community; those who write chainable methods can
stop, and those who want chainable methods can get a wider choice of
libraries that were previously too verbose in use for their taste.For this sort of chaining I'd propose, in addition to the cascade
operator,
the chain operator. This would work like the cascade operator except
that
the return of the function is implicitly the first parameter of the next
argument in the chainI don't like this idea, at all - I think we should aim for something
consistent with other languages. Per the Dart spec:A cascaded method invocation expression of the form e..suffix is
equivalent to the expression (t){t.suffix; return t;}(e).In other words, the expression always evaluates as the object, which
is fine for most cases, e.g. for everything you're doing with
chainable methods today - which can't actually return any values,
since they return $this. Note that something like PSR-7's HTTP
immutable models are actually factory methods, e.g. use-cases not
applicable to the cascade operator.The other marginal use case perhaps is cases where a builder of some
sort ends with a call to a factory method - this can be done without
adding a second operator, by using parens in a similar fashion to Dart
and others, e.g.:$object = ($builder ->>setFoo(1) ->>setBar(2) ->>setBaz(3) )->build();
Or obviously:
$builder ->>setFoo(1) ->>setBar(2) ->>setBaz(3); $object = $builder->build();
Regarding syntax - I feel the natural choice, e.g. similar to "." vs
"..", would be a longer arrow --> but that's ambiguous. (decrement
operator, greater than operator)The proposed |> operator looks horrible and is very awkward to type,
at least on both American and Danish keyboard - I use both. (it reads
like "or greater than" in my mind, so much so that glancing over
Sara's proposal earlier, I didn't even understand what it was.)Would something like ->> be ambiguous as well? That's fairly close too
- a double-headed arrow, not unlike the double dots of other
languages...
IMHO double-headed arrow looks pretty nice and whole idea is great. Very
big +1 for this feature
Still interested: what's the actual added value of this sort of
operator?Marco Pivetta
But for that to currently work setSomething must return $this. A
cascade
operator would avoid this. What about => ?$a->setSomething('here')=>setSomethingElse('there');
This will be ambiguous with array syntax, e.g.
$foo = [ $a->foo() => bar() ]
could be a call to $a->foo() for the key and to bar() for the value, or
just a chain ending with the array value $a->bar().Naming things is hard. Inventing operators is harder. ;)
Also note that it's the first call that needs a special operator, in
order to discard the normal return and substitute the left-hand side,
otherwise mixed chains become ambiguous and probably horrible to
implement
in the engine:$foo->bar()->baz()=>bob() // what is $this inside bob() ?
For this sort of chaining I'd propose, in addition to the cascade
operator,the chain operator. This would work like the cascade operator except
that
the return of the function is implicitly the first parameter of the
next
argument in the chain. Something like this?$a->add(1, 2):>subtract(3):>add(4); // return 4.
This exact feature was proposed a couple of months ago by Sara Golemon.
See https://wiki.php.net/rfc/pipe-operator and the accompanying
discussion http://marc.info/?t=146196088900001&r=4&w=2As far as I know, it's not been formally withdrawn, so might be
resurrected in some for for 7.2. It might be interesting to see a
cascade
operator at the same time, but some of the same concerns that came up
in
that discussion will probably apply to any proposal.Regards,
Rowan Collins
[IMSoP]
2016-07-11 13:30 GMT-03:00 Rasmus Schultz rasmus@mindplay.dk:
Regarding syntax - I feel the natural choice, e.g. similar to "." vs
"..", would be a longer arrow --> but that's ambiguous. (decrement
operator, greater than operator)
Yes, it's ambiguous. It will also get confused for the (in)famous "goes to
operator":
http://stackoverflow.com/questions/1642028/what-is-the-name-of-the-operator
what's the actual added value of this sort of operator?
The only direct value to the language is brevity - mere convenience.But I think the biggest indirectly added value, is that this will
discourage people from the chainable methods anti-pattern - so this
has value to the community; those who write chainable methods can
stop, and those who want chainable methods can get a wider choice of
libraries that were previously too verbose in use for their taste.
As one of those proponents of chained methods / returning $this, I quite
like this proposal. It neatly handles many (although not all) of the
use cases for method chaining, specifically the most controversial ones,
while not having any of the same potential issues. (Eg, when composing
and delegating the method call to another object, you have to do some
$this juggling.) It would not remove the need for all method chaining,
but it would remove the need for the controversial ones that Rasmus is
complaining about while letting people like me still write nicely
compact and readable code. :-)
(Modulo implementation details and debating the operator symbol for a
few weeks, as per usual.)
--Larry Garfield
Would something like ->> be ambiguous as well? That's fairly close too
- a double-headed arrow, not unlike the double dots of other
languages...
A few weeks ago I proposed a syntax for setting multiple object properties
in an expression with the result as the object (
http://news.php.net/php.internals/93662). Both that feature and the cascade
operator can be considered instances of a broader need to manipulate an
object as an expression with the result being the object itself, and either
feature could support both calling a method and setting a property:
$this->setBlah(
Blah::create(4)
->>foo = $foo
->>baz = ((new Baz())
->>markFixed()
->>label = "Hello"
)
->>setBot(9)
);
$this->setBlah(
Blah::create(4) {
foo = $foo,
baz = new Baz() {
markFixed(),
label = "Hello",
},
setBot(9),
}
);
Would it be incohesive to have two different syntaxes for setting
properties and calling methods, like below?
$this->setBlah(
Blah::create(4) {
foo = $foo,
baz = (new Baz())
->>markFixed()
{ label = "Hello" },
}
->>setBot(9)
);
If so, some consideration should be made as to which syntax is preferred to
solve this problem generally for both setting properties and calling
methods.
I prefer the "obj { .. }" syntax because it mirrors object literals in
JSON/JavaScript, object initializers in C# and C/C++ and Haskell record
syntax, is particularly concise when nested to create large trees of
objects, and avoids the noise of "->>" on each line.
If so, some consideration should be made as to which syntax is preferred
to solve this problem generally for both setting properties and calling
methods.I prefer the "obj { .. }" syntax because it mirrors object literals in
JSON/JavaScript, object initializers in C# and C/C++ and Haskell record
syntax, is particularly concise when nested to create large trees of
objects, and avoids the noise of "->>" on each line.
All the examples you've given for that syntax are for initialisation,
and I'm not sure how readable it would be for the general "do several
things in a row" case:
$counter {
count()
,
count()
,
count()
,
count()
}
Thinking about it, this becomes something more like a "with" keyword:
with ( $counter ) {
count()
;
count()
;
count()
;
count()
;
}
If with(){} could be used as an expression, you'd get something
remarkably close to your initialiser example:
$this->setBlah(
with ( Blah::create(4) ) {
foo = $foo;
baz = with ( new Baz() ) {
markFixed();
label = "Hello";
};
setBot(9);
}
);
I'm not sure if this is a good thing or not - with() statements exist in
a few languages, with varying degrees of acceptance. I don't remember
seeing one used as an expression like that before, though.
There's a definite awkwardness of what to put on the left side of
property assignments too...
with ( new Foo ) { var = 1 }
with ( new Foo ) { $var = 1 }
with ( new Foo ) { ->var = 1 }
Overall, it's an interesting idea, but the details are fiddly, and the
gains marginal, IMO.
Regards,
Rowan Collins
[IMSoP]
Thinking about it, this becomes something more like a "with" keyword
I was starting to think along these lines.
I do like the idea of being able to specify the context for a block -
though, as you say, this feature raises issues, and in e.g.
Javascript, these days, it is now strongly discouraged, and prevented
in strict mode.
I do think it would have some potentially pretty awesome uses though -
for example, in templates, assuming this statement would have a
non-block counterpart (like if and while have) you could "import" a
class with HTML helper methods in this way:
<?php with ($html_helper): ?>
<h1><?= escape($title) ?></h1>
<?php endwith ?>
It also potentially lets you create highly convenient DSLs:
with ($parser_helper) {
$INT = seq(optional(char("-")), plus(digit()));
}
This would have potentially really good IDE support and offline
inspections as well.
I guess the biggest issue is what happens when you wrap multiple scopes?
with ($a) {
with ($b) {
foo();
}
}
What happens in this case? I don't think there's any good answer to
that question - if neither $b or $a implements foo(), the only thing
the compiler could do, is dispatch $b->__call("foo", []) ... so you'd
need some really complex logic and rules, and these constructs could
have some pretty unpredictable performance implications as well.
There's also the question of what happens in this case:
with ($a) {
bar();
}
function bar() {
with ($b) {
fud();
}
}
Does it work in the local scope only, or would it be able to resolve
as $a->fud() ? That's crazy, and it probably should work only in the
lexical scope of the current function, but anyways - I think this
feature raises a lot of ugly questions with no elegant answers...
If so, some consideration should be made as to which syntax is preferred
to solve this problem generally for both setting properties and calling
methods.I prefer the "obj { .. }" syntax because it mirrors object literals in
JSON/JavaScript, object initializers in C# and C/C++ and Haskell record
syntax, is particularly concise when nested to create large trees of
objects, and avoids the noise of "->>" on each line.All the examples you've given for that syntax are for initialisation, and
I'm not sure how readable it would be for the general "do several things in
a row" case:$counter {
count()
,
count()
,
count()
,
count()
}Thinking about it, this becomes something more like a "with" keyword:
with ( $counter ) {
count()
;
count()
;
count()
;
count()
;
}If with(){} could be used as an expression, you'd get something remarkably
close to your initialiser example:$this->setBlah(
with ( Blah::create(4) ) {
foo = $foo;
baz = with ( new Baz() ) {
markFixed();
label = "Hello";
};
setBot(9);
}
);I'm not sure if this is a good thing or not - with() statements exist in a
few languages, with varying degrees of acceptance. I don't remember seeing
one used as an expression like that before, though.There's a definite awkwardness of what to put on the left side of property
assignments too...with ( new Foo ) { var = 1 }
with ( new Foo ) { $var = 1 }
with ( new Foo ) { ->var = 1 }Overall, it's an interesting idea, but the details are fiddly, and the gains
marginal, IMO.Regards,
Rowan Collins
[IMSoP]
On Tue, Jul 12, 2016 at 6:36 PM, Rowan Collins rowan.collins@gmail.com
wrote:
Thinking about it, this becomes something more like a "with" keyword:
with ( $counter ) {
count()
;
count()
;
count()
;
count()
;
}
The semicolon suggests those lines are arbitrary statements, in which case
it's ambiguous whether count()
is a method or a normal function. And what
if I want to call a function count()
but $counter happens to have a count()
method I didn't know about when writing the code?
That's why I used a comma, to be clear the lines are not statements but
rather only things that can go on the right of "->" (property access
(assignment) or method call). The lines have their own definition, like the
lines of an array literal do.
I'm not sure if this is a good thing or not - with() statements exist in a
few languages, with varying degrees of acceptance. I don't remember seeing
one used as an expression like that before, though.
I'm only aware of the "with" statement in JavaScript, which is considered a
real language wart because all it does is take an object and adds it to
your scope chain, so that without knowing precisely what properties the
object has, you can't predict what local variables are going to be masked
and which ones aren't.
There's a definite awkwardness of what to put on the left side of property
assignments too...
with ( new Foo ) { var = 1 }
with ( new Foo ) { $var = 1 }
with ( new Foo ) { ->var = 1 }
If the definition of the lines is "what can go right of ->" then it follows
that the first line is correct, which also happens to be the most concise
one :) .
Overall, it's an interesting idea, but the details are fiddly, and the
gains marginal, IMO.
I think the details are only fiddly if the lines can be arbitrary
statements.
Regards,
Rowan Collins
[IMSoP]