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 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”.
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.