Introducing Pipe to return RFC
https://wiki.php.net/rfc/pipe_to_return
In short, RFC proposes piping expression result to return to be used
together with pipe operator to reduce cognitive load
"Hello World" |> strlen http://www.php.net/strlen(...) |> return;
--
Vadim Dvorovenko
Introducing Pipe to return RFC
https://wiki.php.net/rfc/pipe_to_return
In short, RFC proposes piping expression result to
returnto be used together with pipe operator to reduce cognitive load"Hello World" |> strlen http://www.php.net/strlen(...) |> return;
Vadim Dvorovenko
Interesting, and thank you for the RFC. As a programmer though, I find being able to scan the left-most columns for return statements quite useful. Having to now also scan the right-most when pipes are involved (which could even mean off the edge of the screen) ... not ideal.
I get I could just ... not do that ... but since this seems to be about cognitive load, I think this would actually add additional cognitive load instead of removing it. Often, you need to find where the function returns; more often than you need to read actual pipes.
— Rob
31.01.2026 20:45, Rob Landers:
I get I could just ... not do that ... but since this seems to be
about cognitive load, I think this would actually add additional
cognitive load instead of removing it. Often, you need to find where
the function returns; more often than you need to read actual pipes.
03.02.2026 22:13, Tim Düsterhus:
That said, I agree with the others that I find the proposal a step
backwards in terms of readability. Having thereturnat the start of
the line is a clear indicator that the function ends there and it is
only a “single bit” of information I have to keep in mind while I
continue reading until I find the semicolon ending the statement. It
is also consistent with how assignments - the other primary use case
for pipes - start with the target variable and the equals before the
“pipe expression”. I think it would not be wrong to think ofreturn
as “assigning to the return value”.
Thanks for these comments. I find them among the most valuable in this
discussion. They really made me think for a long time and experiment a lot.
To discuss them, I've been going through and trying to come up with
different code options. Unfortunately, there aren't any successful
examples yet in the real projects and libraries I work with. If you
could provide links to repositories where pipe operator is actively
used, I'd be very grateful. So for now I apologize for the fictitious
example.
Let's take this complex function
function process_request($request)
{
if ($request instanceof ManageEmployeeRequest) {
if ($request instanceof CreateEmployeeRequest) {
return $request |> logRequest(...) |>
validateCreateEmployeeRequest(...) |> processCreateEmployee(...) |>
formatEmployeeDataAsArray(...) |> convertArrayToJsonResponse(...);
} elseif ($request instanceof UpdateEmployeeRequest) {
return $request |> logRequest(...) |>
validateUpdateEmployeeRequest(...) |> processUpdateEmployee(...) |>
formatEmployeeDataAsArray(...) |> convertArrayToJsonResponse(...);
} elseif ($request instanceof DeleteEmployeeRequest) {
$request |> logRequest(...) |>
validateDeleteEmployeeRequest(...) |> processDeleteEmployee(...);
return [] |> convertArrayToJsonResponse(...);
} else {
throw new InvalidArgumentException("Unknown client
request type");
}
}
if ($request instanceof ManageClientRequest) {
if ($request instanceof CreateClientRequest) {
return $request |> validateCreateClientRequest(...) |>
processCreateClient(...) |> formatClientDataAsArray(...) |>
convertArrayToJsonResponse(...);
} elseif ($request instanceof UpdateClientRequest) {
return $request |> validateUpdateClientRequest(...) |>
processUpdateClient(...) |> formatClientDataAsArray(...) |>
convertArrayToJsonResponse(...);
} elseif ($request instanceof DeleteClientRequest) {
$request |> validateDeleteClientRequest(...) |>
processDeleteClient(...);
return convertArrayToJsonResponse([]);
} else {
throw new InvalidArgumentException("Unknown client
request type");
}
}
throw new InvalidArgumentException("Unknown request type");
}
When we read this function, we are actually more interested in the
structure of conditions and returns than in the actual actions. In this
case, of course, return at the beginning of the line is indeed more
readable. Here I completely agree with you.
But in real life we won't use such long lines and the chain will be
split into several lines. Here I used different code styles in one block
for clarity.
function process_request($request)
{
if ($request instanceof ManageEmployeeRequest) {
if ($request instanceof CreateEmployeeRequest) {
$request
|> logRequest(...)
|> validateCreateEmployeeRequest(...)
|> processCreateEmployee(...)
|> formatEmployeeDataAsArray(...)
|> convertArrayToJsonResponse(...)
|> return;
} elseif ($request instanceof UpdateEmployeeRequest) {
return
$request
|> logRequest(...)
|> validateUpdateEmployeeRequest(...)
|> processUpdateEmployee(...)
|> formatEmployeeDataAsArray(...)
|> convertArrayToJsonResponse(...);
} elseif ($request instanceof DeleteEmployeeRequest) {
$loggedRequest = logRequest($loggedRequest);
$validatedRequest =
validateDeleteEmployeeRequest($loggedRequest);
processDeleteEmployee($validatedRequest);
$response = convertArrayToJsonResponse([]);
return $response;
} else {
throw new InvalidArgumentException("Unknown client
request type");
}
}
// ...
}
In my case, the IDE brightly highlights 'if', 'elseif', and 'return'
keywords. In this version, I clearly see the return in the first two
blocks, despite its different placement. The 'return' in the third block
stands out the least; it blends in with the preceding text because there
is no indentation. But when I'm composing a letter, I don't have syntax
highlighting, but the feeling is the same.
The third block looks more like a chain of actions. Of course, it's
written in imperative style.
The first block also looks like a chain of actions. Vision very quickly
stops paying attention to |>. Moreover, it fits seamlessly into the
conditions structure. It's clear that this chain of transformations is
executed under these very conditions. And here it seems to me very
fortunate that after reading the block to the end, we see return, and we
don’t need to look up 6 lines above to remember why we performed all
these actions.
I myself reread the second block several times, trying to understand
whether it really is more convenient to read. But every time I read the
chain of actions to the end, I caught myself thinking that I didn’t want
to go back to check if there was a ‘return’ there. On the contrary,
during the time I was working with this example, my brain developed a
rule, and seeing a vertical chain of '|>', I immediately began to look
at the end of the block to understand whether there is a return at the
end. It's like looking at the first and last lines of a third block,
skipping over everything in between. But with |>, your eye kind of
slides down them, and you get to |> return; much faster.
I also noticed that when I look at the second block, my vision selects
the block starting with $request. For some reason it crops the
return located to the left and above. I think this is because it is a
keyword that has ceased to carry meaning for us, and we perceive it as a
punctuation mark, rather than as carrying meaning.
Hello,
First off, I appreciate the work that has gone into this RFC.
I like the general direction and I am not fully against the idea. That said, I am hesitant to introduce another way to express the same thing, especially when it affects readability.
Speaking only for myself, I agree with Rob’s point that I expect return to appear on the left side. Placing return on the right side inside the pipe makes the statement harder to scan and can be confusing at first glance.
More importantly, I do not think the responsibility for returning should live inside the pipe. Returning is control flow, while the pipe’s core purpose is transforming data. With this change, the pipe no longer clearly communicates “transform this value,” but can instead become responsible for ending execution. I would rather see the pipe produce a result, and keep the return outside the pipe so the intent stays explicit.
Also, while the example below obviously would not work, it highlights the potential ambiguity this introduces. In a long pipeline, it becomes less clear where returning happens and what role the pipe is playing.
function giveMeFourtyTwo() {
return 42
|> return;
}
Also if you put this into the short fn syntax. It will break for the same reasons as above.
fn () => 'hello world'
|> strlen(...)
|> return;
Regards,
Jordi
Jordi Kroon:
I like the general direction and I am not fully against the idea. That
said, I am hesitant to introduce another way to express the same
thing, especially when it affects readability.
When we are speaking about pipe operator, it all looks as trying to
introduce another way to express the same thing, and it affects readability.
As for me,
$temp = "Hello World";
$temp = htmlentities($temp);
$temp = str_split($temp);
$temp = array_map(strtoupper(...), $temp);
return $temp;
and
return array_map(strtoupper(...), str_split(htmlentities("Hello
World")));
were quite effective ways of expressing value transformation flow. But
that RFC was adopted. My RFC is an attempt to quickly eliminate the
cognitive side effects arisen by adding the pipe operator to the language.
The addition of the pipe operator didn't immediately force all language
users to use it. But it did give them the option. Which method
of expression of same thing sould be used in different cases, most
likely will be described later in the coding standards of specific
projects. Likewise, the method proposed here will not be mandatory for
use, but there may be cases where it will be chosen for greater readability.
More importantly, I do not think the responsibility for returning
should live inside the pipe. Returning is control flow, while the
pipe’s core purpose is transforming data. With this change, the pipe
no longer clearly communicates “transform this value,” but can instead
become responsible for ending execution. I would rather see the pipe
produce a result, and keep the return outside the pipe so the intent
stays explicit.Also, while the example below obviously would not work, it highlights
the potential ambiguity this introduces. In a long pipeline, it
becomes less clear where returning happens and what role the pipe is
playing.function giveMeFourtyTwo() {
return 42
|> return;
}
Also if you put this into the shortfnsyntax. It will break for the
same reasons as above.fn () => 'hello world'
|> strlen(...)
|> return;
Our language is now very flexible. But such flexibility would be
impossible if everithing was strictly divided into categories. Take the
throw, for example. It's obviously intended for flow control. However,
it can be used anywhere, like in following example.
return 42 |> fn($val) => throw new RuntimeException($val);
// or
throw throw throw new Exception();
Although such a statements contains ambiguity between return and throw,
or between multiple throws, we don't criticize the language for this
possibility. In such cases, we say the author of the code has chosen an
unfortunate style of expression.
My proposal, on the contrary, does not allow using two types of return
in one expression or using return inside an arrow function. In this
sense, my proposal is quite strict.
You can use either way of expression, but not both at the same time.
Thus, ambiguity is generated not by the programming language itself and
its capabilities, but by those who express their thoughts in it. We
should not limit the expressive instruments of a language simply out of
fear that they will be misused.
--
Vadim Dvorovenko
Jordi Kroon:
function giveMeFourtyTwo() {
return 42
|> return;
}
...
fn () => 'hello world'
|> strlen(...)
|> return;
Thanks for examples. I've already had a test for first case
(https://github.com/vadimonus/php-src/blob/pipe-to-return/Zend/tests/pipe_operator/pipe_to_return/two_returns.phpt)
Now i have added test for second case
(https://github.com/vadimonus/php-src/blob/pipe-to-return/Zend/tests/pipe_operator/pipe_to_return/008.phpt)
to be sure it's not valid and cannot be accidently treated as returning
of (fn () => 'hello world' |> strlen(...)) from function as callable
--
Vadim Dvorovenko
Hi
Am 2026-01-31 06:52, schrieb Vadim Dvorovenko:
Introducing Pipe to return RFC
https://wiki.php.net/rfc/pipe_to_return
In short, RFC proposes piping expression result to
returnto be used
together with pipe operator to reduce cognitive load
Thank you for your RFC. I appreciate how well-written it is and how
carefully you followed the suggested template.
That said, I agree with the others that I find the proposal a step
backwards in terms of readability. Having the return at the start of
the line is a clear indicator that the function ends there and it is
only a “single bit” of information I have to keep in mind while I
continue reading until I find the semicolon ending the statement. It is
also consistent with how assignments - the other primary use case for
pipes - start with the target variable and the equals before the “pipe
expression”. I think it would not be wrong to think of return as
“assigning to the return value”.
While it is true that no one is forced to use the new syntax, I will
inevitably come across it in libraries that decide to use it, thus I
will be forced to know that it is a thing and to understand what it
does. And of course all IDEs and static analyzers will need to learn
about it as well.
For these reasons, I don't believe it meets the cost-benefit ratio.
Best regards
Tim Düsterhus
03.02.2026 22:13, Tim Düsterhus:
That said, I agree with the others that I find the proposal a step
backwards in terms of readability. Having thereturnat the start of
the line is a clear indicator that the function ends there and it is
only a “single bit” of information I have to keep in mind while I
continue reading until I find the semicolon ending the statement. It
is also consistent with how assignments - the other primary use case
for pipes - start with the target variable and the equals before the
“pipe expression”. I think it would not be wrong to think ofreturn
as “assigning to the return value”.
Some time ago, I raised dicussion
(https://news-web.php.net/php.internals/128141) of using pipes for
assignment as well, you also took part in that discussion. Currently, i
started with the RFC for return because it's simplest, to illustrate
main cognitive problem. By the way, returning is something that can not
be implemented function like assign_to.
The main idea is that the pipe operator's main drawback is that it
changes the usual reading direction. Function composition via + doesn't
have this drawback. Maybe if function composition had been implemented
earlier, the pipe operator wouldn't have been adopted. Now the pipe
operator is part of the language, and I'm just trying to expand its use
cases to write more intuitive and native-language-like code.
Look, even simple statements like return $x |> strlen(...); may be
confusing, if you were not familiar with the pipe operator before,
because you do not understand, if you should stop after return $x or
not. On the other side $x |> strlen(...) |> return is more intuitive
in this way, even if you see |> for the first time, reading this line
you understand, that it has two actions, defined by arrows, and it is
some sort of data transfer.
Even though you know |> is called a pipe operator, the return $x |> strlen(...); expression doesn't show how the data is flowing through
the pipes. Something like return strlen(...) <| $x would be much more
expressive in this sense. I had an idea to bring an RFC for the pipe
operator to the left instead of this. But i chose not to invent a new
operator, but simply to expand the grammatical possibilities of using an
existing one.
03.02.2026 22:13, Tim Düsterhus:
While it is true that no one is forced to use the new syntax, I will
inevitably come across it in libraries that decide to use it, thus I
will be forced to know that it is a thing and to understand what it
does. And of course all IDEs and static analyzers will need to learn
about it as well.
Of course, I can't cover all areas, especially proprietary IDEs.
However, I planned to prepare corresponding changes for the following
tools: nikic/PHP-Parser, psalm, phpstan, php-cs-fixer. If this could
affect the vote, I can prepare changes to the this projects before the vote.
Introducing Pipe to return RFC
https://wiki.php.net/rfc/pipe_to_return
In short, RFC proposes piping expression result to
returnto be used
together with pipe operator to reduce cognitive load"Hello World" |> strlen http://www.php.net/strlen(...) |> return;
--
Vadim Dvorovenko
I'm wondering how this would relate to the function composition
operator, noted in the follow-up RFC.
Basically, would "return" make sense at the end of a function
composition chain, and what sense would that be?
When $x |> f1(...) |> f2(...) |> f3(...);
is equivalent to (f1(...) + f2(...) + f3(...))($x);
[Aside: personally I find the suggested order of arguments backwards
from every other instance of function composition I've encountered, but
I suppose I could learn to live with it. But that's another subject.]
and $c = f1(...) + f2(...) + f3(...);
is equivalent to $c = fn($x) => ($x |> f1(...) |> f2(...) |> f3(...));
what happens when "f3(...)" is "return"? The intended behaviour is given
for the first case but not for the others (not even the fourth, which is
still the pipe operator).
04.02.2026 04:58, Morgan:
I'm wondering how this would relate to the function composition
operator, noted in the follow-up RFC.
Basically, would "return" make sense at the end of a function
composition chain, and what sense would that be?When $x |> f1(...) |> f2(...) |> f3(...);
is equivalent to (f1(...) + f2(...) + f3(...))($x);
[Aside: personally I find the suggested order of arguments backwards
from every other instance of function composition I've encountered,
but I suppose I could learn to live with it. But that's another subject.]and $c = f1(...) + f2(...) + f3(...);
is equivalent to $c = fn($x) => ($x |> f1(...) |> f2(...) |> f3(...));what happens when "f3(...)" is "return"? The intended behaviour is
given for the first case but not for the others (not even the fourth,
which is still the pipe operator).
Neither return nor pipe to return can appear inside an arrow function.
So fn($x) => ($x |> f1(...) |> f2(...) |> return) is invalid, same as
fn($x) => (return $x |> f1(...) |> f2(...))
$x |> f1(...) |> f2(...) |> return; can be rewritten as
return (f1(...) + f2(...))($x) or as (f1(...) + f2(...))($x) |> return.
Piping to return should not be perceived as a method of composition. It
is some way of defining data flow, pipe here in plumbing meaning. $x
var goes to f1(), then to f2(), and then to sewerage (return).
On Sat, Jan 31, 2026 at 9:52 AM Vadim Dvorovenko vadim.dvorovenko@gmail.com
wrote:
Introducing Pipe to return RFC
https://wiki.php.net/rfc/pipe_to_return
In short, RFC proposes piping expression result to
returnto be used
together with pipe operator to reduce cognitive load"Hello World" |> strlen http://www.php.net/strlen(...) |> return;
--
Vadim Dvorovenko
Hi Vadim,
thanks for the RFC — the idea of piping into return is interesting.
One question about semantics: what would be the expected behavior if there
is another pipe operand after return? For example:
$foo |> return |> bar(...);
Would this be a compile-time error, a no-op after return, or is return
intended to be a hard terminal in the pipe chain by definition?
Best regards,
Oleksii
04.02.2026 15:16, Oleksii Bulba пишет:
One question about semantics: what would be the expected behavior if
there is another pipe operand after |return|? For example:|$foo |> return |> bar(...);|
Would this be a compile-time error, a no-op after |return|, or is
|return|intended to be a hard terminal in the pipe chain by definition?Best regards,
Oleksii
This would lead to parse-time time error, return |> is not valid
grammar sequence.
Since this raised a lot of questions, I added examples of invalid uses
of the proposed construction from your messages to the RFC.