This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
Hey,
Am 29.04.2016 um 21:58 schrieb Sara Golemon pollita@php.net:
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
Thanks for proposing, so that I am able to show what I don't like about the |> pipe operator:
In general, the pipe operator is hiding anti-patterns. It makes anti-patterns look nicer.
Take the first example:
$ret =
array_merge(
$ret,
getFileArg(
array_map(
function ($x) use ($arg) { return $arg . '/' . $x; },
array_filter(
scandir($arg),
function ($x) { return $x !== '.' && $x !== '..'); }
)
)
)
);
Sure, it looks nicer with the pipe operator, if you really want to nest it. But that nesting is horrible in itself. It should be written as:
$files = array_filter(scandir($arg), function ($x) { $x != '.' && $x != '..'; });
$fullPath = array_map(function($x) use ($arg) { return "$arg/$x"; }, $files);
$allFiles = array_merge($allFiles, getFileArg($fullPath));
By limiting the nesting level to 2, the code gets itself much more readable and in addition, the variable names are self-explaining, so that you can jump right in.
As already mentioned, the |> variant looks nicer than the above, but it still lacks the most important part; documentation in form of obvious variable names after each small step.
It's always nice when code can look nicely, but this doesn't encourage writing understandable code. This RFC is just providing another way to shoot any future readers into the feet.
Readers want to quickly grasp what is going on. Having to follow a whole list of operations whose result is written to a variable $ret is telling them absolutely nothing.
TL;DR:
-1: The |> pipe operator encourages a write-only style.
Bob
Hey,
to me recursion and pipe operator looks really hard to read, I guess this is faster and better readable:
foreach (scandir($arg) as $x) {
if ($x == '.' || $x == '..') {
continue;
}
$x = $arg . '/' . $x;
// inline getFileArg ...
$allFiles[] = $x;
}
Regards
Thomas
Bob Weinand wrote on 30.04.2016 01:37:
Hey,
Am 29.04.2016 um 21:58 schrieb Sara Golemon pollita@php.net:
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operatorThanks for proposing, so that I am able to show what I don't like about the |>
pipe operator:In general, the pipe operator is hiding anti-patterns. It makes anti-patterns
look nicer.Take the first example:
$ret =
array_merge(
$ret,
getFileArg(
array_map(
function ($x) use ($arg) { return $arg . '/' . $x; },
array_filter(
scandir($arg),
function ($x) { return $x !== '.' && $x !== '..'); }
)
)
)
);Sure, it looks nicer with the pipe operator, if you really want to nest it.
But that nesting is horrible in itself. It should be written as:$files = array_filter(scandir($arg), function ($x) { $x != '.' && $x != '..';
});
$fullPath = array_map(function($x) use ($arg) { return "$arg/$x"; }, $files);
$allFiles = array_merge($allFiles, getFileArg($fullPath));By limiting the nesting level to 2, the code gets itself much more readable and
in addition, the variable names are self-explaining, so that you can jump right
in.
As already mentioned, the |> variant looks nicer than the above, but it still
lacks the most important part; documentation in form of obvious variable names
after each small step.It's always nice when code can look nicely, but this doesn't encourage writing
understandable code. This RFC is just providing another way to shoot any future
readers into the feet.
Readers want to quickly grasp what is going on. Having to follow a whole list
of operations whose result is written to a variable $ret is telling them
absolutely nothing.TL;DR:
-1: The |> pipe operator encourages a write-only style.Bob
Hey,
Am 29.04.2016 um 21:58 schrieb Sara Golemon pollita@php.net:
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
Thanks for proposing, so that I am able to show what I don't like about the |> pipe operator:In general, the pipe operator is hiding anti-patterns. It makes anti-patterns look nicer.
Take the first example:
$ret =
array_merge(
$ret,
getFileArg(
array_map(
function ($x) use ($arg) { return $arg . '/' . $x; },
array_filter(
scandir($arg),
function ($x) { return $x !== '.' && $x !== '..'); }
)
)
)
);Sure, it looks nicer with the pipe operator, if you really want to nest it. But that nesting is horrible in itself. It should be written as:
$files = array_filter(scandir($arg), function ($x) { $x != '.' && $x != '..'; });
$fullPath = array_map(function($x) use ($arg) { return "$arg/$x"; }, $files);
$allFiles = array_merge($allFiles, getFileArg($fullPath));By limiting the nesting level to 2, the code gets itself much more readable and in addition, the variable names are self-explaining, so that you can jump right in.
As already mentioned, the |> variant looks nicer than the above, but it still lacks the most important part; documentation in form of obvious variable names after each small step.It's always nice when code can look nicely, but this doesn't encourage writing understandable code. This RFC is just providing another way to shoot any future readers into the feet.
Readers want to quickly grasp what is going on. Having to follow a whole list of operations whose result is written to a variable $ret is telling them absolutely nothing.TL;DR:
-1: The |> pipe operator encourages a write-only style.Bob
I disagree. The absence of an intermediate variable name does not
inherently hinder readability/debuggability (nor does its presence
inherently improve it). It just makes more work for the code author to
come up with potentially pointless variable names.
What I see in |> is an end-run around the "can't call methods on arrays
because they're not collections" problem. Viz, if we were dealing with
an actual collection (as in Javascript, or many PHP libraries), we could
do the following:
$ret = $scandir($arg)
->filter(function($x) { return $x !== '.' && $x != '..'; })
->map(function ($x) use ($arg) { return $arg . '/' . $x; })
->apply('getFileArg')
->merge($ret);
Which would be even nicer. (This is also where the short-lambda
proposal would come in useful.) That's perfectly readable to me, and
very clear what's happening.
My concern, if anything, is that |> feels more like a stop-gap hack
around our clunky an uncomposable array handling rather than a solution
to it. I like the idea, I just wonder if we shouldn't fix the
underlying problem and make functions better composable in the first place.
--Larry Garfield
Hi Sara,
I have to take exception to the section of the RFC titled "File
collection Example", where you take a block of code and re-write it to
be more readable, using the RFC's syntax.
However, it is not the proposed new syntax that makes the code more
readable - it is solely due to writing it in the correct order that
makes the code more readable. This can be done using current PHP
syntax, so that the code is:
$dirEntries = scandir($arg);
$files = array_filter($filesAndDirectories, function($x) { return
$x !== '.' && $x != '..'; });
$fullPaths = array_map(function ($x) use ($arg) { return $arg . '/' .
$x; }, $files)
$fileInfo = getFileArg($fullPaths);
$ret = array_merge($ret, $fileListInfo);
Using the proposed new syntax to rewrite it without the intermediate
variables like this.
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
Does not now show a marked improvement in readability.
Showing the benefits of an RFC by comparing 'apples to oranges', is at
least to some extent 'pulling a fast one'. Do you have any examples
where the original code is not terrible, that show an increase in
readability? (By implication, I am not persuaded by the PSR 7 ones.)
Larry Garfield wrote:
The absence of an intermediate variable name does not inherently hinder readability/debuggability (nor does its presence inherently improve it).
I disagree strongly, the presence of intermedite variables does help
the code be more understandable. It gives semantic meaning to the
steps in involved in the process, as well as giving variables that can
be watched in a debugger.
If people want the excitement of not being able to figure out what the
intermediate variables mean, then they can already do so by using
terrible variable names.
From the first "PSR7 Example", we could write the code like:
$v = getGlobals();
$v = parseRequest($v);
$request = buildPsr7Request($v);
If you truly believe that not having semantically meaningful
intermediate variable names, you would have no problem with code being
written like this, right?
Personally, I always use semantically meaningful variable names, as
they make life so much easier for any person who needs to read the
code.
cheers
Dan
Personally, I always use semantically meaningful variable names, as
they make life so much easier for any person who needs to read the
code.
AND one can easily see if there are points in the sequence where one
does need to handle alternate results. I will avoid saying 'errors'
because and empty array will not throw an exception. But I CAN perhaps
now see why some people object to not throwing errors where they rely on
exceptions while others of us simply anticipate that there are
situations where an alternate result needs alternate action?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Le dimanche 1 mai 2016, 00:20:45 Dan Ackroyd a écrit :
However, it is not the proposed new syntax that makes the code more
readable - it is solely due to writing it in the correct order that
makes the code more readable. This can be done using current PHP
syntax, so that the code is:$dirEntries = scandir($arg);
$files = array_filter($filesAndDirectories, function($x) { return
$x !== '.' && $x != '..'; });
$fullPaths = array_map(function ($x) use ($arg) { return $arg . '/' .
$x; }, $files)
$fileInfo = getFileArg($fullPaths);
$ret = array_merge($ret, $fileListInfo);Using the proposed new syntax to rewrite it without the intermediate
variables like this.$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);Does not now show a marked improvement in readability.
Yeah, then why is the first code you wrote containing errors and not the second one?
I have to read carefully the first one to see that the variables used in the code are not the same one returned from the line above.
There is no need for this in the second one which is much clearer, each call reuses the return from the function above.
I had no opinion on this RFC until people started to explain using 100 variables for such simple things is the best solution.
Am 30.04.2016 um 01:37 schrieb Bob Weinand:
TL;DR:
-1: The |> pipe operator encourages a write-only style.
I second that emotion.
Hi!
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
I think this takes the syntax too much towards Perl 6 direction. There
is a lot of fun in inventing cool signs like |> and making them do
nontrivial cool stuff. There's much less fun when you try to figure out
what such code is actually doing - and especially why it is not doing
what you thought it is doing. Brian Kernigan once said:
"Everyone knows that debugging is twice as hard as writing a program in
the first place. So if you're as clever as you can be when you write it,
how will you ever debug it?"
I think this applies not only to debugging, but also to reading programs
in general. If we are "as clever as we can be" with syntax, the program
becomes unreadable.
For this feature specifically, I see nothing wrong with assigning
intermediate results to variables - moreover, I see an advantage to it
both in reading (you tell people what you expect to have happened in
this stage) and in debugging (you can actually see what happened and
check whether what you thought should be happening actually happened).
If we could achieve the elegance of Unix pipes, I'd agree that it is
simple enough that we can see what is going on - but having it rely on
two magic things which are in different part of expression and
context-dependent ($$ means different things in different places) is too
much for me.
--
Stas Malyshev
smalyshev@gmail.com
I would say that this makes the entire functional approach:
- more readable
- easier to debug (woah, we can haz line numbers for failures!)
Here's the pseudo-code for a typical request/response dispatch cycle:
$request = getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($app)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);
Here's what it would look like with a separate approach (more
business-oriented):
buildRequest() // (basically the first part of the previous example here)
|> validate($$)
|> convertToCommand($$)
|> execute($$)
|> convertToViewModel($$)
|> render($$)
|> convertToHttpResponse($$)
|> emit($$)
I think this is much more readable/clear than any event-driven or
procedural approach.
We know exactly what is going on, we will get clear stack traces (this
could need some fine-tuning of the current PR - I didn't try it out yet)
and we will be able to introduce intermediate steps with ease. In addition
to all that, this will ease reactive programming by a lot, as piping is a
natural fit for streams and all abstractions around streams.
The syntax is also already effective in F# (you can write very expressive
and easy to understand code with it) and now also landed in Hack.
I'd say +10 to this proposal :D
Marco Pivetta
Hi!
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operatorI think this takes the syntax too much towards Perl 6 direction. There
is a lot of fun in inventing cool signs like |> and making them do
nontrivial cool stuff. There's much less fun when you try to figure out
what such code is actually doing - and especially why it is not doing
what you thought it is doing. Brian Kernigan once said:"Everyone knows that debugging is twice as hard as writing a program in
the first place. So if you're as clever as you can be when you write it,
how will you ever debug it?"I think this applies not only to debugging, but also to reading programs
in general. If we are "as clever as we can be" with syntax, the program
becomes unreadable.For this feature specifically, I see nothing wrong with assigning
intermediate results to variables - moreover, I see an advantage to it
both in reading (you tell people what you expect to have happened in
this stage) and in debugging (you can actually see what happened and
check whether what you thought should be happening actually happened).If we could achieve the elegance of Unix pipes, I'd agree that it is
simple enough that we can see what is going on - but having it rely on
two magic things which are in different part of expression and
context-dependent ($$ means different things in different places) is too
much for me.--
Stas Malyshev
smalyshev@gmail.com
I like the proposal, but readability should be on the forefront of things
that need to be done right. Yes, there are people that will make it an
unreadable mess, but we have code reformat, so if the syntax is verbose
enought to be read at a glance, I'm all for it.
+1 as long as syntax gets a hard look at and examples in RFC are amended to
show it better :)
I would say that this makes the entire functional approach:
- more readable
- easier to debug (woah, we can haz line numbers for failures!)
Here's the pseudo-code for a typical request/response dispatch cycle:
$request = getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($app)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);Here's what it would look like with a separate approach (more
business-oriented):buildRequest() // (basically the first part of the previous example here)
|> validate($$)
|> convertToCommand($$)
|> execute($$)
|> convertToViewModel($$)
|> render($$)
|> convertToHttpResponse($$)
|> emit($$)I think this is much more readable/clear than any event-driven or
procedural approach.
We know exactly what is going on, we will get clear stack traces (this
could need some fine-tuning of the current PR - I didn't try it out yet)
and we will be able to introduce intermediate steps with ease. In addition
to all that, this will ease reactive programming by a lot, as piping is a
natural fit for streams and all abstractions around streams.The syntax is also already effective in F# (you can write very expressive
and easy to understand code with it) and now also landed in Hack.I'd say +10 to this proposal :D
Marco Pivetta
Hi!
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operatorI think this takes the syntax too much towards Perl 6 direction. There
is a lot of fun in inventing cool signs like |> and making them do
nontrivial cool stuff. There's much less fun when you try to figure out
what such code is actually doing - and especially why it is not doing
what you thought it is doing. Brian Kernigan once said:"Everyone knows that debugging is twice as hard as writing a program in
the first place. So if you're as clever as you can be when you write it,
how will you ever debug it?"I think this applies not only to debugging, but also to reading programs
in general. If we are "as clever as we can be" with syntax, the program
becomes unreadable.For this feature specifically, I see nothing wrong with assigning
intermediate results to variables - moreover, I see an advantage to it
both in reading (you tell people what you expect to have happened in
this stage) and in debugging (you can actually see what happened and
check whether what you thought should be happening actually happened).If we could achieve the elegance of Unix pipes, I'd agree that it is
simple enough that we can see what is going on - but having it rely on
two magic things which are in different part of expression and
context-dependent ($$ means different things in different places) is too
much for me.--
Stas Malyshev
smalyshev@gmail.com
I think this is much more readable/clear than any event-driven or
procedural approach.
We know exactly what is going on, we will get clear stack traces (this
could need some fine-tuning of the current PR - I didn't try it out yet)
and we will be able to introduce intermediate steps with ease. In addition
to all that, this will ease reactive programming by a lot, as piping is a
natural fit for streams and all abstractions around streams.The syntax is also already effective in F# (you can write very expressive
and easy to understand code with it) and now also landed in Hack.I'd say +10 to this proposal :D
I am with Stanislav on this one or rather meh. It also goes against what
you usually advocate: there should be only one way to achieve things (I
remember your /fluent interface/ example very well). Unless you changed
your mind regarding that. Hence, it is nice syntactic sugar but it does
not allow us to solve problems that we cannot solve already.
--
Richard "Fleshgrinder" Fussenegger
This feature has nothing to do with fluent interfaces, nor has their flaws.
The readability of piped processing for functional code is simply a
no-brainer here, and doesn't require any API changes in function signatures
either: it is not "a different way of doing the same thing".
I think this is much more readable/clear than any event-driven or
procedural approach.
We know exactly what is going on, we will get clear stack traces (this
could need some fine-tuning of the current PR - I didn't try it out yet)
and we will be able to introduce intermediate steps with ease. In
addition
to all that, this will ease reactive programming by a lot, as piping is a
natural fit for streams and all abstractions around streams.The syntax is also already effective in F# (you can write very expressive
and easy to understand code with it) and now also landed in Hack.I'd say +10 to this proposal :D
I am with Stanislav on this one or rather meh. It also goes against what
you usually advocate: there should be only one way to achieve things (I
remember your /fluent interface/ example very well). Unless you changed
your mind regarding that. Hence, it is nice syntactic sugar but it does
not allow us to solve problems that we cannot solve already.--
Richard "Fleshgrinder" Fussenegger
This feature has nothing to do with fluent interfaces, nor has their flaws.
The readability of piped processing for functional code is simply a
no-brainer here, and doesn't require any API changes in function signatures
either: it is not "a different way of doing the same thing".
This is the example code from the RFC written in a normal procedural way.
$files = scandir($arg);
$files = array_filter($files, function ($x) {
return $x !== '.' && $x !== '..';
});
$files = array_map(function ($x) use ($arg) {
return $path . '/' . $x;
}, $files);
$ret = array_merge($ret, getFileArg($files));
This is the example code with the pipe operator.
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
Definitely looks like "a different way of doing the same thing" to me.
So does the initial super ugly example code of nesting.
Again, I am not really against the proposal nor do I argue against the
fact that it improves readability of certain code constructs. I am just
meh on it because such code can pretty much always be rewritten in a way
that makes it more readable, easier to debug, and even faster. In the
example code we have:
array_filter with O(n)
array_map with O(n)
array_merge with O(∑ array_i, i != 1) and in our case O(n) where n
equals the total count of elements in $files/$$.
In my universe getFileArg
(note the absence of a plural form) would
operate on a single path and not on an array of paths:
foreach (new DirectoryIterator($arg) as $path) {
if ($path->isDot() === false) {
$ret[] = getFileArg($arg . DIRECTORY_SEPARATOR
. $path);
}
}
I cannot tell if this construct is really equivalent to the example from
the RFC (because I do not know what $ret contains) but it should do
exactly the same. It is more readable, easier to debug, and much faster.
Now you may argue that the example is simply flawed. Well, maybe, but
functional programming is nice if you have a language that is
functional. The only functional part in PHP are callbacks, everything
else is just plain old procedural + OO and importing more functional
concepts will not change that. However, I am not saying that every
functional concept makes no sense just that the pipes are not very
appealing to me because I only see "a different way of doing the same
thing" with slower code. :P
--
Richard "Fleshgrinder" Fussenegger
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
This should actually be formatted as to be fair contender to the
procedural example:
$ret = scandir($arg)
|> array_filter($$, function ($x) {
return $x !== '.' && $x !== '..';
})
}> array_map(function ($x) use ($arg) {
return $arg . '/' . $x;
}, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
Not a big difference now...
$files = scandir($arg);
$files = array_filter($files, function ($x) {
return $x !== '.' && $x !== '..';
});
$files = array_map(function ($x) use ($arg) {
return $path . '/' . $x;
}, $files);
$ret = array_merge($ret, getFileArg($files));
...the only thing that bugs me in the procedural code is the fact that
array_filter and array_map take their arguments in different order. :P
--
Richard "Fleshgrinder" Fussenegger
Here's the pseudo-code for a typical request/response dispatch cycle:
Hi Marco,
Could you clarify something for me?
If I submitted a pull-request to you, with variables named like this:
$v = buildRequest();
$v = validate($v);
$v = convertToCommand($v);
$v = execute($v);
$v = convertToViewModel($v);
$v = render($v);
$v = convertToHttpResponse($v);
emit($v);
There is no chance that you would accept it. You would (correctly)
observe that the variables for the intermediate steps have meaningless
names and so the code is hard to reason about. I don't think removing
the presence of the intermediate variables and replacing them with $$
improves this situation.
What is it that having a special pipe operator that make this
acceptable, other than it covers up the lack of names for the
intermediate variables, by using a different syntax?
Marco wrote:
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.
cheers
Dan
Marco wrote:
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.
A little off topic, but in line with the debate in general on performance.
The interesting bit relating to error handling was the 'lack of
strings'. But the idea of just building a longer and longer list of
error codes and then having to manage translations as well. One of the
major advantages of adding a database to the production system is that
lists like this can be generically managed, and selecting a translated
version is simply a matter of a flag on the database. But code wise
inside PHP much of what is essentially text can be managed as simple
integer values. Add the same message process to the good paths as well
and a lot of string activity can be relegated to the display rendering
stage.
--
Lester Caine - G8HFL
-----------------------------Marco wrote:Marco wrote:Marco wrote:Marco
Marco wrote:
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.Marco wrote:
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.wrote:
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.
Relevant: https://youtu.be/UvD1VjRvGIk
I could imagine how having inline branches could be a useful thing for
functional programming, for various scenarios, including being able to
'inline' error handling, to be nearer the source of errors. However
this RFC does not propose that.
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Hi!
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
I think this takes the syntax too much towards Perl 6 direction. There
is a lot of fun in inventing cool signs like |> and making them do
nontrivial cool stuff. There's much less fun when you try to figure out
what such code is actually doing - and especially why it is not doing
what you thought it is doing. Brian Kernigan once said:"Everyone knows that debugging is twice as hard as writing a program in
the first place. So if you're as clever as you can be when you write it,
how will you ever debug it?"I think this applies not only to debugging, but also to reading programs
in general. If we are "as clever as we can be" with syntax, the program
becomes unreadable.For this feature specifically, I see nothing wrong with assigning
intermediate results to variables - moreover, I see an advantage to it
both in reading (you tell people what you expect to have happened in
this stage) and in debugging (you can actually see what happened and
check whether what you thought should be happening actually happened).If we could achieve the elegance of Unix pipes, I'd agree that it is
simple enough that we can see what is going on - but having it rely on
two magic things which are in different part of expression and
context-dependent ($$ means different things in different places) is too
much for me.
See, I take that quote in the exact opposite direction. I find chaining
methods to be far, far less "clever" than deeply nesting them inside
each other. We shouldn't force pointless intermediate variables on
people for the sake of readability. The fair comparison is the deeply
nested version vs. the chained version, and in that comparison the
readability of the chained version is far, far better.
Lester pointed out error handling and branching. True, the pipe
chaining wouldn't handle that, but it doesn't claim to. Neither does
directly nesting the function calls inside each other. If you do need
to pause, examine a value, and branch, then yes that's where you want an
intermediate variable to analyze and nothing has changed. However,
passing an array to array_filter()
then passing the result to
array_map()
is always type safe (because array_filter still returns an
array even if it filters down to 0 items), and array_map on an empty
array is essentially a no-op, so I'm comfortable doing so, and wish I
could do so more often with less fugly syntax. :-)
In a way... Sara, this syntax feels like it's only one step removed from
promises; if any of the chained steps involve IO, it becomes basically
what promises are for. Am I barking down the wrong tree, or is there
something we could connect there? (I don't mean "reject this in favor
of promises", but more "is there something we can do here to be forward
thinking, since lots of people want to see async in core PHP?")
--Larry Garfield
However,
passing an array to array_filter()
then passing the result to array_map()
is always type safe (because array_filter still returns an
array even if it filters down to 0 items), and array_map on an empty
array is essentially a no-op, so I'm comfortable doing so, and wish I
could do so more often with less fugly syntax. :-)
God I hate crap software ... TRYING to reply without top post ... but Samsung is incapable!
What I was trying to comment on was that trapping that no result is returned may be what is needed, so handling the empty array rather than a null return just means having to remember what you do use :(
Sent from my android device so quoting is crap ... need to kill these painful email clients!
-----Original Message-----
From: lester@lsces.co.uk
To: internals@lists.php.net, Larry Garfield larry@garfieldtech.com
Sent: Sat, 30 Apr 2016 19:35
Subject: Re: [PHP-DEV] [RFC] Pipe Operator
However,
passing an array to array_filter()
then passing the result to array_map()
is always type safe (because array_filter still returns an
array even if it filters down to 0 items), and array_map on an empty
array is essentially a no-op, so I'm comfortable doing so, and wish I
could do so more often with less fugly syntax. :-)
God I hate crap software ... TRYING to reply without top post ... but Samsung is incapable!
What I was trying to comment on was that trapping that no result is returned may be what is needed, so handling the empty array rather than a null return just means having to remember what you do use :(
What follows is a terrible idea and I don't mean to propose it as a
solution, but to spark further conversation:
$result = getData()
?> doStuffWith($$)
:> actOnFailureOf($$);
Basically, a chaining ternary such that the result of the condition
expression propagates to the true/false expressions. Not really the
same feature at this point, but a similar chaining-friendly take on
the standard ternary. Just thinking out loud...
-Sara
God I hate crap software ... TRYING to reply without top post ... but Samsung is incapable!
What I was trying to comment on was that trapping that no result is returned may be what is needed, so handling the empty array rather than a null return just means having to remember what you do use :(
What follows is a terrible idea and I don't mean to propose it as a
solution, but to spark further conversation:$result = getData()
?> doStuffWith($$)
I thought this was the "PHP, stop messing around with my HTML" operator?
:> actOnFailureOf($$);
Basically, a chaining ternary such that the result of the condition
expression propagates to the true/false expressions. Not really the
same feature at this point, but a similar chaining-friendly take on
the standard ternary. Just thinking out loud...
--
Regards,
Mike
What follows is a terrible idea and I don't mean to propose it as a
solution, but to spark further conversation:$result = getData()
?> doStuffWith($$)I thought this was the "PHP, stop messing around with my HTML" operator?
Look, I said it was a terrible idea. Terrible ideas don't deserve to
be well crafted. :p
-Sara
Hi!
See, I take that quote in the exact opposite direction. I find chaining
methods to be far, far less "clever" than deeply nesting them inside
If you tell me that syntax like "foo() |> bar($$)" is more natural or
intuitive or readily understandable to anyone with any programming and
PHP background than "$fooResult = foo(); bar($fooResult); " then we
indeed have exactly opposite understanding of what is natural and intuitive.
I think that overly clever is inventing cryptic syntax to save a couple
of keystrokes and rearrange code in unusual pattern that looks unlike
the code used to look before and resembles some other language (this
time it's F#? how many languages PHP should be at once - can we get a
dozen?)
each other. We shouldn't force pointless intermediate variables on
Why you think they are pointless? And you do produce them, you just hide
them behind $$ so you can not actually neither check them nor understand
which value goes where without tracing through the code with your finger.
people for the sake of readability. The fair comparison is the deeply
nested version vs. the chained version, and in that comparison the
readability of the chained version is far, far better.
No, it's not a fair comparison. You shouldn't do deep nesting, and you
shouldn't do cryptic syntax either. Variables are not evil. They are
there to make things easier for you. Use them.
intermediate variable to analyze and nothing has changed. However,
passing an array toarray_filter()
then passing the result to
array_map()
is always type safe (because array_filter still returns an
array even if it filters down to 0 items), and array_map on an empty
array is essentially a no-op, so I'm comfortable doing so, and wish I
could do so more often with less fugly syntax. :-)
I appreciate the careful choice of the example on RFC - indeed, in some
very carefully chosen cases (mostly with arrays) you could get away with
chaining without getting into trouble. In most cases where it would be
used, though, that would mean not checking for errors, forgetting corner
cases and so on. And that's how people would use this, because none of
the existing functions were developed to work with this pattern, so you
can only get away with it due to luck.
In strictly typed languages like Haskell, they use types like Option and
Maybe to enable this pattern - essentially, to enable functions to
operate on more than one simple type of value. However, it doesn't look
in place in PHP, and it would require much more deep restructuring to
actually make it work than just having |> as an operator.
of promises", but more "is there something we can do here to be forward
thinking, since lots of people want to see async in core PHP?")
I'm not sure what promises have to do with inventing strange syntax to
pass result of one function as a parameter to another.
Stas Malyshev
smalyshev@gmail.com
Hi!
See, I take that quote in the exact opposite direction. I find chaining
methods to be far, far less "clever" than deeply nesting them inside
If you tell me that syntax like "foo() |> bar($$)" is more natural or
intuitive or readily understandable to anyone with any programming and
PHP background than "$fooResult = foo(); bar($fooResult); " then we
indeed have exactly opposite understanding of what is natural and intuitive.
I think that overly clever is inventing cryptic syntax to save a couple
of keystrokes and rearrange code in unusual pattern that looks unlike
the code used to look before and resembles some other language (this
time it's F#? how many languages PHP should be at once - can we get a
dozen?)
each other. We shouldn't force pointless intermediate variables on
Why you think they are pointless? And you do produce them, you just hide
them behind $$ so you can not actually neither check them nor understand
which value goes where without tracing through the code with your finger.
people for the sake of readability. The fair comparison is the deeply
nested version vs. the chained version, and in that comparison the
readability of the chained version is far, far better.
No, it's not a fair comparison. You shouldn't do deep nesting, and you
shouldn't do cryptic syntax either. Variables are not evil. They are
there to make things easier for you. Use them.
intermediate variable to analyze and nothing has changed. However,
passing an array toarray_filter()
then passing the result to
array_map()
is always type safe (because array_filter still returns an
array even if it filters down to 0 items), and array_map on an empty
array is essentially a no-op, so I'm comfortable doing so, and wish I
could do so more often with less fugly syntax. :-)
I appreciate the careful choice of the example on RFC - indeed, in some
very carefully chosen cases (mostly with arrays) you could get away with
chaining without getting into trouble. In most cases where it would be
used, though, that would mean not checking for errors, forgetting corner
cases and so on. And that's how people would use this, because none of
the existing functions were developed to work with this pattern, so you
can only get away with it due to luck.
In strictly typed languages like Haskell, they use types like Option and
Maybe to enable this pattern - essentially, to enable functions to
operate on more than one simple type of value. However, it doesn't look
in place in PHP, and it would require much more deep restructuring to
actually make it work than just having |> as an operator.
of promises", but more "is there something we can do here to be forward
thinking, since lots of people want to see async in core PHP?")
I'm not sure what promises have to do with inventing strange syntax to
pass result of one function as a parameter to another.
Stas Malyshev
smalyshev@gmail.com
In a way... Sara, this syntax feels like it's only one step removed from
promises; if any of the chained steps involve IO, it becomes basically
what promises are for. Am I barking down the wrong tree, or is there
something we could connect there? (I don't mean "reject this in favor
of promises", but more "is there something we can do here to be forward
thinking, since lots of people want to see async in core PHP?")--Larry Garfield
I see the connection to promises too; it's not just you :)
I might be thinking about it wrong, but I think that "one step" you're
looking for is handling the failure case (Sara kinda mentioned it in a
later post). Promises are essentially matching two cases:
Success/Ok/Some, and Failure/Error/None. This RFC seems to only handle
the first case. Sure, you could use exception handling:
try {
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x !=
'..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x;
}, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
catch (Throwable $e) {
}
But not all failures or "errors" are actually exceptions. I'm not sure
how to approach this cleanly without monads, really.
The other issue is with closures and callbacks. Unless, at some point in
the future when the heroes of PHP gift us with async in the core pipe
operator is redefined, async operations need to be resumable later.
Consider a small hypothetical I/O example:
$data = new Socket()
|> (function ($socket) {
// Um, how do I yield/await here? The next pipe function
// expects an immediate return...
return $socket->connect(/* ... */);
})($$) // <-- also, this is awkward...
|> readUntil($$, '\n')
The benefit of promises is that everything is eventually defined in
terms of callbacks. That way things can be executed whenever. From my
understanding (correct me if I'm wrong) the pipe operator does not
enable this.
Also, +1 for the idea, and +1 for considering the promises case! Using
the pipe operator for async operations would be super cool!
--
Stephen
On Sun, May 1, 2016 at 4:05 AM, Larry Garfield larry@garfieldtech.com
wrote:
In a way... Sara, this syntax feels like it's only one step removed from
promises; if any of the chained steps involve IO, it becomes basically what
promises are for. Am I barking down the wrong tree, or is there something
we could connect there? (I don't mean "reject this in favor of promises",
but more "is there something we can do here to be forward thinking, since
lots of people want to see async in core PHP?")
If you had async/await, you could of course use |> to chain async
operations in an async function by using "await $$" in place of "$$" (since
$$ would be the Promise/Awaitable):
$ret = scandir_async($arg)
|> array_filter(await $$, ...)
|> ...
That's quite neat, IMO, and the features are still orthogonal.
You could introduce a new operator to combine the "|>" and "await", say
"a|>", but to add an operator just to combine two others would seem a bit
arbitrary.
You could also generalise |> as a monadic bind (with Promise as the monad
for asynchronicity) but I don't know how that would fit in PHP land without
a meaningful way to turn an expression into a closure. It could be added
later as something like "|>>" if need be.
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
I can see the attraction, but question it's usability in real time code.
It assumes everything has a single linear flow? Once one has branches
handing different results from the various stages it's usability looks
questionable. While one may well be able to break things down with
exceptions handling all of the invalid returns, if one needs to 'break
the flow' to put in an alternate path then what is supposedly a simple
sequence of steps becomes difficult to rework to allow for the change?
Exceptions should be reserved for out of the ordinary events, not
changes in work flow because different paths are equally valid?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Relevant: https://youtu.be/UvD1VjRvGIk
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operatorI can see the attraction, but question it's usability in real time code.
It assumes everything has a single linear flow? Once one has branches
handing different results from the various stages it's usability looks
questionable. While one may well be able to break things down with
exceptions handling all of the invalid returns, if one needs to 'break
the flow' to put in an alternate path then what is supposedly a simple
sequence of steps becomes difficult to rework to allow for the change?
Exceptions should be reserved for out of the ordinary events, not
changes in work flow because different paths are equally valid?--
Lester Caine - G8HFLContact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Relevant: https://youtu.be/UvD1VjRvGIk
Trimming the now useless error code ....
As I said in the message ... no problem if you simply have a SINGLE
pathway through the code. That video simply assumes that anything that
is not success is an error. MUCH of my code base has turntables or three
way switches which in addition to possible errors, you have a lot more
than one 'success' output.
The pipe operator does not provide a work flow as demonstrated in that
video ...
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Relevant: https://youtu.be/UvD1VjRvGIk
Trimming the now useless error code ....
As I said in the message ... no problem if you simply have a SINGLE
pathway through the code. That video simply assumes that anything that
is not success is an error. MUCH of my code base has turntables or three
way switches which in addition to possible errors, you have a lot more
than one 'success' output.The pipe operator does not provide a work flow as demonstrated in that
video ...
So in essence, any feature that doesn't fit your codebase is wrong. About
tight?
--
Lester Caine - G8HFLContact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
As I said in the message ... no problem if you simply have a SINGLE
pathway through the code. That video simply assumes that anything that
is not success is an error. MUCH of my code base has turntables or three
way switches which in addition to possible errors, you have a lot more
than one 'success' output.
If it doesn't suit your code base design then simply don't use the
feature ... I don't see the need for you to raise objections here simply
because it doesn't fit your purposes..
As I said in the message ... no problem if you simply have a SINGLE
pathway through the code. That video simply assumes that anything that
is not success is an error. MUCH of my code base has turntables or three
way switches which in addition to possible errors, you have a lot more
than one 'success' output.If it doesn't suit your code base design then simply don't use the
feature ... I don't see the need for you to raise objections here simply
because it doesn't fit your purposes..
The problem is not with ME using this sort of re-factoring of code, it's
managing and maintaining code others have 'improved' but applying it. I
have a similar problem with chained operators in classes like PDO.
Invariably the 'edge cases' where nothing is returned at a point in the
chain are not handled or throw an exception, where ACTUALLY you want to
process the 'mid point' of the chain and create a split in the flow.
Someone having tidied what was perhaps nicely documented and structured
code into the one single chain may not be the right thing when missing
elements need to be added to the workflow.
Having now watched more of the linked talk I find the one thing that I
do like with that model ... trap exceptions and put them back into the
workflow ... The second half of the video is well worth watching! I like
the idea that you keep the action open to do retries such as resending
the email if it failed first time. That is not a simple exception on the
workflow of a pipe operation but is a major difference between compiled
languages and scripting process.
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
I like this a lot.
--
Paul M. Jones
http://paul-m-jones.com
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
I like this idea, for the same reason I like chained method calls - the
code reads left to right, rather than inside to outside.
One problem with that, though, is that the output from the whole
expression ends up on the left, if you want to save it out somewhere. To
take an example from the RFC:
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
Once you've read through this whole sequence of steps, you have to go
right back to the beginning to find that the result is saved into "$ret".
It's not clear to me whether the RHS in the current implementation can
be an arbitrary expression, since all the examples use function calls at
every step; would this be valid?
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$)
|> ($ret = $$);
Even if it is, it looks a bit ugly; what about adding an extra operator
for assignment, like "|>=" or "|=>"?
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;
I think this would also improve the situation around debugging and
error-handling, because you could more freely mix piped and non-piped
code by "breaking the chain" with an assigment.
Let's say I want to add a condition just before getFileArg(); with the
current version I've got to:
- go to the beginning of the chain, and assign to something other than $ret
- go to where I want to break the chain, and reintroduce the $ret assignment
- add my check in the gap, using the variable I just added at the
beginning of the chain
$fileList = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$);
if ( someCheck($fileList) {
something();
}
$ret = getFileArg($$)
|> array_merge($ret, $$);
The syntax is fighting me here, making me jump around the code. But if
assignment was always on the end of the chain, I would only need to make
changes at the point where I was breaking the chain. The basic pattern
would be:
|=> $tempVar; // terminate the chain and capture the value
// do stuff with $tempVar
$tempVar // restart the chain
So:
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|=> $fileList;
if ( someCheck($fileList) {
something();
}
$fileList
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;
If I don't need the condition any more, I can delete lines 4 to 8, and
I've got back my original chain.
Again, I'm not clear from the RFC if it's currently valid to use
"return" in a piped expression, but it makes sense to me for the same
reasons outlined above. This example is from the Hack docs, where it
puts the "return" on the left, breaking the flow:
return $arr
|> array_map($x ==> $x->getNumber(), $$)
|> array_filter($$, $x ==> $x % 2 == 0)
|> count($$);
To me, that reads better as:
$arr
|> array_map($x ==> $x->getNumber(), $$)
|> array_filter($$, $x ==> $x % 2 == 0)
|> count($$)
|> return $$;
Personally, I'd quite like it if the chain had to have a left-to-right
form like this, and always constituted a statement, not an expression.
Regards,
--
Rowan Collins
[IMSoP]
Ho Rowan,
Le 01/05/2016 01:14, Rowan Collins a écrit :
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operatorI like this idea, for the same reason I like chained method calls - the
code reads left to right, rather than inside to outside.One problem with that, though, is that the output from the whole
expression ends up on the left, if you want to save it out somewhere. To
take an example from the RFC:$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);Once you've read through this whole sequence of steps, you have to go
right back to the beginning to find that the result is saved into "$ret".It's not clear to me whether the RHS in the current implementation can
be an arbitrary expression, since all the examples use function calls at
every step; would this be valid?scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$)
|> ($ret = $$);Even if it is, it looks a bit ugly; what about adding an extra operator
for assignment, like "|>=" or "|=>"?scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;I think this would also improve the situation around debugging and
error-handling, because you could more freely mix piped and non-piped
code by "breaking the chain" with an assigment.Let's say I want to add a condition just before getFileArg(); with the
current version I've got to:
- go to the beginning of the chain, and assign to something other than $ret
- go to where I want to break the chain, and reintroduce the $ret
assignment- add my check in the gap, using the variable I just added at the
beginning of the chain$fileList = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$$);
if ( someCheck($fileList) {
something();
}
$ret = getFileArg($$)
|> array_merge($ret, $$);The syntax is fighting me here, making me jump around the code. But if
assignment was always on the end of the chain, I would only need to make
changes at the point where I was breaking the chain. The basic pattern
would be:|=> $tempVar; // terminate the chain and capture the value
// do stuff with $tempVar
$tempVar // restart the chainSo:
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|=> $fileList;
if ( someCheck($fileList) {
something();
}
$fileList
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;If I don't need the condition any more, I can delete lines 4 to 8, and
I've got back my original chain.Again, I'm not clear from the RFC if it's currently valid to use
"return" in a piped expression, but it makes sense to me for the same
reasons outlined above. This example is from the Hack docs, where it
puts the "return" on the left, breaking the flow:return $arr
|> array_map($x ==> $x->getNumber(), $$)
|> array_filter($$, $x ==> $x % 2 == 0)
|> count($$);To me, that reads better as:
$arr
|> array_map($x ==> $x->getNumber(), $$)
|> array_filter($$, $x ==> $x % 2 == 0)
|> count($$)
|> return $$;Personally, I'd quite like it if the chain had to have a left-to-right
form like this, and always constituted a statement, not an expression.Regards,
I find your suggestion brilliant, including ending with a 'return'
statement.
Just a question : do we really need a different operator ? I may be
wrong but '|> $ret' or '|> return $$' doesn't seem ambiguous to me (the
parser should be able to recognize an assignable LHS or 'return'). I
even suggest we use a '... |> $var |> ...' syntax to assign intermediate
variables.
I am currently writing a message proposing a way to complement these
'piped calls' with a way to get rid of the '$$' placeholder. This will
address the long-running sadness of argument order.
Regards
François
Let's say I want to add a condition just before getFileArg(); with the
current version I've got to:
- go to the beginning of the chain, and assign to something other than $ret
- go to where I want to break the chain, and reintroduce the $ret
assignment- add my check in the gap, using the variable I just added at the
beginning of the chain$fileList = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$$);
if ( someCheck($fileList) {
something();
}
$ret = getFileArg($$)
|> array_merge($ret, $$);The syntax is fighting me here, making me jump around the code. But if
assignment was always on the end of the chain, I would only need to make
changes at the point where I was breaking the chain. The basic pattern
would be:|=> $tempVar; // terminate the chain and capture the value
// do stuff with $tempVar
$tempVar // restart the chainSo:
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|=> $fileList;
if ( someCheck($fileList) {
something();
}
$fileList
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;If I don't need the condition any more, I can delete lines 4 to 8, and
I've got back my original chain.
Could you use a closure instead to accomplish this? (Again yes, Sara
could you clarify if this is permitted?)
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x !=
'..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x;
}, $$)
|> (function($fileList) {
if (someCheck($fileList)) {
something();
}
return $fileList;
})($$)
|> getFileArg($$)
|> array_merge($ret, $$);
Not completely the best, but perhaps there's some sort of an idea here?
--
Stephen
Let's say I want to add a condition just before getFileArg(); with the
current version I've got to:
- go to the beginning of the chain, and assign to something other than
$ret- go to where I want to break the chain, and reintroduce the $ret
assignment- add my check in the gap, using the variable I just added at the
beginning of the chain$fileList = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..';
})
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$$);
if ( someCheck($fileList) {
something();
}
$ret = getFileArg($$)
|> array_merge($ret, $$);The syntax is fighting me here, making me jump around the code. But if
assignment was always on the end of the chain, I would only need to make
changes at the point where I was breaking the chain. The basic pattern
would be:|=> $tempVar; // terminate the chain and capture the value
// do stuff with $tempVar
$tempVar // restart the chainSo:
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..';
})
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$$)
|=> $fileList;
if ( someCheck($fileList) {
something();
}
$fileList
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;If I don't need the condition any more, I can delete lines 4 to 8, and
I've got back my original chain.Could you use a closure instead to accomplish this? (Again yes, Sara could
you clarify if this is permitted?)$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x !=
'..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$$)
|> (function($fileList) {
if (someCheck($fileList)) {
something();
}
return $fileList;
})($$)
|> getFileArg($$)
|> array_merge($ret, $$);Not completely the best, but perhaps there's some sort of an idea here?
--
Stephen--
Doesn't Nikic's scalar objects (https://github.com/nikic/scalar_objects)
more or less achieve the same thing while also cleaning up the std lib?
$ret = scandir($arg)
->filter(function(){})
->map(function(){})
->merge($someOtherArray);
Terry Cullen
Doesn't Nikic's scalar objects (https://github.com/nikic/scalar_objects)
more or less achieve the same thing while also cleaning up the std lib?$ret = scandir($arg)
->filter(function(){})
->map(function(){})
->merge($someOtherArray);
It is indeed a much better approach. The more I read and think about the
pipe operator the more I am against it.
--
Richard "Fleshgrinder" Fussenegger
Doesn't Nikic's scalar objects (https://github.com/nikic/scalar_objects)
more or less achieve the same thing while also cleaning up the std lib?$ret = scandir($arg)
->filter(function(){})
->map(function(){})
->merge($someOtherArray);It is indeed a much better approach. The more I read and think about the
pipe operator the more I am against it.
The difference with scalar objects is:
A) We need to agree on what methods these objects are going to have.
((And we won't agree))
We could maybe hedge our bets with a ->invoke(Callable) method that
pushes the value into an arbitrary closure, but that's just adding
complexity to... avoid adding complexity... o.O
B) It's not just about scalars.
$foo = SomeLibrary::create(...)
|> SomeOtherLibrary::render($$)
|> $foo->send($$);
Scalar objects don't help this case at all, and since the object
instances are coming from some third-party library, making them
instantiate extended children instead isn't necessarily a trivial
thing (arguments about poorly designed libraries aside).
I like the scalar objects approach as well. Heck, I wrote one three
years ago: https://github.com/sgolemon/objectifier but I don't think
it solves the same problem space as pipe chaining.
-Sara
I like the scalar objects approach as well. Heck, I wrote one three
years ago: https://github.com/sgolemon/objectifier but I don't think
it solves the same problem space as pipe chaining.
Which does beg the question ... "Just how many problem spaces are
currently under discussion?" ... There is a space for some additional
extensions that provided experimental solutions to some of the perceived
problems which will then allow people to play with ideas before they
become main stream, but there simply seem to be too many proposals
currently just because 7.1 will allow them when perhaps we still need to
refine just what was added in 7. If all the current rfc's get accepted
there will be a large chunk of new documentation which needs digesting
UNLESS it is simply optionally enabled in 7.1
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Which does beg the question ... "Just how many problem spaces are
currently under discussion?" ... There is a space for some additional
extensions that provided experimental solutions to some of the perceived
problems which will then allow people to play with ideas before they
become main stream, but there simply seem to be too many proposals
currently just because 7.1 will allow them when perhaps we still need to
refine just what was added in 7. If all the current rfc's get accepted
there will be a large chunk of new documentation which needs digesting
UNLESS it is simply optionally enabled in 7.1
It's worth noting that I left the targeting section of the RFC at
"7.Next" because I actually agree that 7.1 has been a very busy minor
and I wouldn't be at all disappointed by deferring this to 7.2. The
last thing any of us wants is Ze'ev belting out another "Give the
Language a Rest" missive. :)
-Sara
The difference with scalar objects is:
A) We need to agree on what methods these objects are going to have.
((And we won't agree))
The nice thing here is that we could start with the things that we agree
on and develop it from there further.
We could maybe hedge our bets with a ->invoke(Callable) method that
pushes the value into an arbitrary closure, but that's just adding
complexity to... avoid adding complexity... o.O
That would be pure evil.
B) It's not just about scalars.
$foo = SomeLibrary::create(...)
|> SomeOtherLibrary::render($$)
|> $foo->send($$);Scalar objects don't help this case at all, and since the object
instances are coming from some third-party library, making them
instantiate extended children instead isn't necessarily a trivial
thing (arguments about poorly designed libraries aside).I like the scalar objects approach as well. Heck, I wrote one three
years ago: https://github.com/sgolemon/objectifier but I don't think
it solves the same problem space as pipe chaining.
The pipe operator is also just a work around for poorly designed
libraries in this case and yields more poorly designed libraries.
--
Richard "Fleshgrinder" Fussenegger
The difference with scalar objects is:
A) We need to agree on what methods these objects are going to have.
((And we won't agree))The nice thing here is that we could start with the things that we agree
on and develop it from there further.
Which is precisely why we'll get paralyzed.
We could maybe hedge our bets with a ->invoke(Callable) method that
pushes the value into an arbitrary closure, but that's just adding
complexity to... avoid adding complexity... o.OThat would be pure evil.
Agreed. It's an attempt to force a square peg into a round hole. But
focusing on OOP chaining will always leave this gap.
The pipe operator is also just a work around for poorly designed
libraries in this case and yields more poorly designed libraries.
Pretending that poorly designed libraries exist is naîve. PHP is
dominated by poorly designed software but it manages to work because
it responds with practicality.
It's practical to provide a functional version of object method chaining.
-Sara
The pipe operator is also just a work around for poorly designed
libraries in this case and yields more poorly designed libraries.Pretending that poorly designed libraries exist is naîve. PHP is
dominated by poorly designed software but it manages to work because
it responds with practicality.It's practical to provide a functional version of object method chaining.
I really do not know what you want to tell me with that first sentence.
The solution for the problem is already baked into PHP and usable:
intermediate variables with meaningful names. Yes, this might sometimes
be overly verbose. Yes, this might tempt some people to create
meaningless variable names. All in all no argument that was brought up
so far showed that this kind of operator is really useful to solve
something that cannot be already solved. It is always just about source
code formatting and laziness to write out some variables.
Sorry
--
Richard "Fleshgrinder" Fussenegger
Pretending that poorly designed libraries exist is naîve.
I really do not know what you want to tell me with that first sentence.
Ooops, missed a negation when I typed it out.
"Pretending that poorly designed libraries DON'T exist is naîve."
The solution for the problem is already baked into PHP and usable:
intermediate variables with meaningful names. Yes, this might sometimes
be overly verbose. Yes, this might tempt some people to create
meaningless variable names. All in all no argument that was brought up
so far showed that this kind of operator is really useful to solve
something that cannot be already solved. It is always just about source
code formatting and laziness to write out some variables.
As I've said already. Yes, intermediate variables do address this
style of coding. Yes, this proposal is syntactic sugar.
Intermediate variables also add cognitive overhead of their own in
cataloging all the various intermediates used in a large function. By
removing the explicit intermediate variables and replacing them with
unnamed temporaries, the code becomes easier to read because there's
less unnecessary assignments cluttering up the space.
-Sara
Intermediate variables also add cognitive overhead of their own in
cataloging all the various intermediates used in a large function. By
removing the explicit intermediate variables and replacing them with
unnamed temporaries, the code becomes easier to read because there's
less unnecessary assignments cluttering up the space.
Doesn't this assume that you understand just what each stage of the
pipeline is returning? Part of the criticism of PHP is that functions
are not consistent, so do you end up with a list of functions that can
be used and a list that can't? The returns are not what the pipeline is
handling?
I can understand the advantage of making a consistent interface but as
we all understand, that has to co-exist with the current procedural one.
https://github.com/nikic/scalar_objects is another attempt at supporting
a different style of working which looks a lot more practical than
continuing to shoehorn bits into the 'old style' API?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Ooops, missed a negation when I typed it out.
"Pretending that poorly designed libraries DON'T exist is naîve."
I am not pretending that they do not exist, quite the contrary, I
explicitly stated that they exist and that I fear that this syntactic
sugar yields more of them in the future.
As I've said already. Yes, intermediate variables do address this
style of coding. Yes, this proposal is syntactic sugar.Intermediate variables also add cognitive overhead of their own in
cataloging all the various intermediates used in a large function. By
removing the explicit intermediate variables and replacing them with
unnamed temporaries, the code becomes easier to read because there's
less unnecessary assignments cluttering up the space.
Still waiting for a real life example that illustrates exactly that. All
examples and code I have seen so far is either extremely constructed
(the request-application-response thingy that is now part of the RFC) or
can be trivially rewritten to be super expressive and readable (the
original from the RFC and most in this thread).
$request = getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);
Ask, don't tell!
final class RequestBuilder {
public static function fromGlobals() {
return new static($_GLOBALS);
}
public function buildPsr7Request() {
$parsed_request = $this->parseRequest();
return new Psr7Request($parsed_request);
}
}
$request = RequestBuilder::fromGlobals()->buildPsr7Request();
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($app)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);
Ask, don't tell!
final class ResponseBuilder {
public static function fromGlobals() {
return new static($_GLOBALS);
}
public function build() {
$this->loadConfig();
$this->buildDic();
$this->buildApp();
$this->buildRouter();
$this->buildDispatcher();
$this->dispatchBusinessLogic();
$this->parseResponse();
return $this->response;
}
}
$response = ResponseBuilder::fromGlobals()->build();
The third is exactly the same ...
Now my favorite:
$ret =
array_merge(
$ret,
getFileArg(
array_map(
function ($x) use ($arg) { return $arg . '/' . $x; },
array_filter(
scandir($arg),
function ($x) { return $x !== '.' && $x !== '..'); }
)
)
)
);
I already rewrote it in another message but once more with the most
relevant parts of my original message:
array_filter with O(n)
array_map with O(n)
array_merge with O(∑ array_i, i != 1) and in our case O(n) where n
equals the total count of elements in $files/$$.
In my universe getFileArg
(note the absence of a plural form) would
operate on a single path and not on an array of paths:
foreach (new DirectoryIterator($arg) as $path) {
if ($path->isDot() === false) {
$ret[] = getFileArg($arg . DIRECTORY_SEPARATOR
. $path);
}
}
Ocramius mentioned that the pipe operator would be super useful in async
libraries and stuff but I am lacking real world examples here that
illustrate how this new operator would make those experimental stuff
benefit from it. Especially benefit besides scalar objects.
--
Richard "Fleshgrinder" Fussenegger
Ooops, missed a negation when I typed it out.
"Pretending that poorly designed libraries DON'T exist is naîve."
I am not pretending that they do not exist, quite the contrary, I
explicitly stated that they exist and that I fear that this syntactic
sugar yields more of them in the future.As I've said already. Yes, intermediate variables do address this
style of coding. Yes, this proposal is syntactic sugar.Intermediate variables also add cognitive overhead of their own in
cataloging all the various intermediates used in a large function. By
removing the explicit intermediate variables and replacing them with
unnamed temporaries, the code becomes easier to read because there's
less unnecessary assignments cluttering up the space.Still waiting for a real life example that illustrates exactly that. All
examples and code I have seen so far is either extremely constructed
(the request-application-response thingy that is now part of the RFC) or
can be trivially rewritten to be super expressive and readable (the
original from the RFC and most in this thread).
I have to disagree, I haven't seen an example of rewriting the original
example from the RFC in a more expressive AND readable way. Yes, you can
use lots of intermediate variables, but this makes the code HARDER to read
QUICKLY which is entirely the point of a syntatic sugar addition like this.
Syntactic sugar makes a language sweeter, and more palatable. Like cane
sugar makes your cookies taste good, but doesn't make them more nutritious.
$request = getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);Ask, don't tell!
final class RequestBuilder {
public static function fromGlobals() { return new static($_GLOBALS); } public function buildPsr7Request() { $parsed_request = $this->parseRequest(); return new Psr7Request($parsed_request); }
}
$request = RequestBuilder::fromGlobals()->buildPsr7Request();
This breaks dependency injection, and makes testing harder. You now depend
on an array of data which is accessed internally, and can only minimally
control it's contents in a testing environment.
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($app)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);Ask, don't tell!
final class ResponseBuilder {
public static function fromGlobals() { return new static($_GLOBALS); } public function build() { $this->loadConfig(); $this->buildDic(); $this->buildApp(); $this->buildRouter(); $this->buildDispatcher(); $this->dispatchBusinessLogic(); $this->parseResponse(); return $this->response; }
}
$response = ResponseBuilder::fromGlobals()->build();
Again, how do I dependency inject a config during testing to make sure I
build the dic correctly? I'm assuming you're using a bunch of temporary
properties? Also you just made adding a step significantly harder, what if
from building the app, I need to build another layer build for the router,
for |> I add ONE line of code, for yours I edit the build function in the
right location, and change the build router function to load from a
different property, and build another boilerplate function to build this
new thing, and add another property to hold this new thing... WHOA that's a
lot more steps, and a MUCH higher risk of a mistake!
The third is exactly the same ...
Now my favorite:
$ret =
array_merge(
$ret,
getFileArg(
array_map(
function ($x) use ($arg) { return $arg . '/' . $x; },
array_filter(
scandir($arg),
function ($x) { return $x !== '.' && $x !== '..'); }
)
)
)
);I already rewrote it in another message but once more with the most
relevant parts of my original message:array_filter with O(n)
array_map with O(n)
array_merge with O(∑ array_i, i != 1) and in our case O(n) where n
equals the total count of elements in $files/$$.In my universe
getFileArg
(note the absence of a plural form) would
operate on a single path and not on an array of paths:foreach (new DirectoryIterator($arg) as $path) {
if ($path->isDot() === false) {
$ret[] = getFileArg($arg .DIRECTORY_SEPARATOR
. $path);
}
}
Again, you're solution is to use OOP, but the pipe operator's purpose is to
make procedural code cleaner. Not one of your solutions maintained a
procedural interface, and therefore is not an argument against the |>
operator. The purpose of this operator is syntactic sugar, which "is syntax
within a programming language that is designed to make things easier to
read or to express. It makes the language "sweeter" for human use: things
can be expressed more clearly, more concisely, or in an alternative style that
some may prefer."[1] (emphasis mine). Its not designed to solve a problem
of how can I do this thing, but how can I write this thing such that my
team might be able to understand it better.
Yes, you can rewrite these examples in an entirely different way, but now
you're not comparing apples to even other fruit (procedural vs OO is not
the same). The point of this is to take a very common pattern (nesting many
procedural calls) and make it easier to read and manage later.
Ocramius mentioned that the pipe operator would be super useful in async
libraries and stuff but I am lacking real world examples here that
illustrate how this new operator would make those experimental stuff
benefit from it. Especially benefit besides scalar objects.--
Richard "Fleshgrinder" Fussenegger
I'm personally +1 on this proposal. The examples I've seen where using lots
of intermediate variables are bad in my book, they create unnecessary
variables in scope, lead to more easily mistyping an intermediate name (as
we saw in a previous reply, showing how using intermediate variables was
better!), and generally make the code not line up to see what's happening.
If I look down the left hand side of the |> examples, I can very easily see
what's happening in order.
I have to disagree, I haven't seen an example of rewriting the original
example from the RFC in a more expressive AND readable way. Yes, you can
use lots of intermediate variables, but this makes the code HARDER to read
QUICKLY which is entirely the point of a syntatic sugar addition like this.
Syntactic sugar makes a language sweeter, and more palatable. Like cane
sugar makes your cookies taste good, but doesn't make them more nutritious.
Opinions you know but I am still waiting for that example.
This breaks dependency injection, and makes testing harder. You now depend
on an array of data which is accessed internally, and can only minimally
control it's contents in a testing environment.
Why do you need dependency injection in the most outer point of your
application? This should be tested with your acceptance/integration
tests and nothing else. How many unit tests have you written for your
Symfony app kernel lately?
A rather extreme example of such a /most outer/ but you know how it is
with constructed examples:
http://www.yegor256.com/2014/10/03/di-containers-are-evil.html#the-right-way
Again, how do I dependency inject a config during testing to make sure I
build the dic correctly? I'm assuming you're using a bunch of temporary
properties? Also you just made adding a step significantly harder, what if
from building the app, I need to build another layer build for the router,
for |> I add ONE line of code, for yours I edit the build function in the
right location, and change the build router function to load from a
different property, and build another boilerplate function to build this
new thing, and add another property to hold this new thing... WHOA that's a
lot more steps, and a MUCH higher risk of a mistake!
Again, this is the most outer place of everything. I mean, all
frameworks and applications were and are able to solve these issues
easily, nicely, and testable. Just because my /proposed/ solution to an
example that is constructed in the first place is not as nice as those
solutions does not make the examples better. :P
Again, you're solution is to use OOP, but the pipe operator's purpose is to
make procedural code cleaner. Not one of your solutions maintained a
procedural interface, and therefore is not an argument against the |>
operator. The purpose of this operator is syntactic sugar, which "is syntax
within a programming language that is designed to make things easier to
read or to express. It makes the language "sweeter" for human use: things
can be expressed more clearly, more concisely, or in an alternative style that
some may prefer."[1] (emphasis mine). Its not designed to solve a problem
of how can I do this thing, but how can I write this thing such that my
team might be able to understand it better.Yes, you can rewrite these examples in an entirely different way, but now
you're not comparing apples to even other fruit (procedural vs OO is not
the same). The point of this is to take a very common pattern (nesting many
procedural calls) and make it easier to read and manage later.
foreach (scandir($arg) as $path) {
if ($path !== '.' && $path !== '..') {
$ret[] = getFileArg($arg . DIRECTORY_SEPARATOR
. $path);
}
}
What can I say ...
--
Richard "Fleshgrinder" Fussenegger
I have to disagree, I haven't seen an example of rewriting the original
example from the RFC in a more expressive AND readable way. Yes, you can
use lots of intermediate variables, but this makes the code HARDER to
read
QUICKLY which is entirely the point of a syntatic sugar addition like
this.
Syntactic sugar makes a language sweeter, and more palatable. Like cane
sugar makes your cookies taste good, but doesn't make them more
nutritious.Opinions you know but I am still waiting for that example.
Waiting for what example? There's been plenty of examples showing how
existing options, rewritten with |> are easier. If you disagree, that's an
opinion, other's agree. If people think its better, why not add it for
them? Like every other feature discussed on this list, nothing is forcing
you to use it.
From: ocramius@gmail.com
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($app)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);Without the pipe operator, it could look like the following:
$config = loadConfig();
$dic = buildDic(config);
$app = getApp($dic);
$router getRouter($app);
$dispatcher = getDispatcher($router, $request);
$businessResponse = dispatchBusinessLogic($dispatcher, $request, new
Response());
$renderedResponse = renderResponse($businessResponse);
$psr7Response = buildPsr7Response($renderedResponse);
$response = emit($psr7Response);That's a lot of unnecessary assignments, however, and the variable names
don't even provide any additional readability benefits because the function
names are already self-documenting.
The first example, with |> is much easier to QUICKLY see what's happening,
because all the function calls are aligned, so I can read quickly top to
bottom knowing what's happening. The second requires much more cognitive
interaction to understand what's going on. Sure you could reformat it to
align all the =, now you have lots of white-space between your variables
and what you're assigning to them... equally as bad in my book.
This breaks dependency injection, and makes testing harder. You now
depend
on an array of data which is accessed internally, and can only minimally
control it's contents in a testing environment.Why do you need dependency injection in the most outer point of your
application? This should be tested with your acceptance/integration
tests and nothing else. How many unit tests have you written for your
Symfony app kernel lately?
None, I don't write Symfony apps, however they wrote quite a few [1].
A rather extreme example of such a /most outer/ but you know how it is
with constructed examples:http://www.yegor256.com/2014/10/03/di-containers-are-evil.html#the-right-way
GROSS. This would not be allowed any where I do code reviews.
Again, how do I dependency inject a config during testing to make sure I
build the dic correctly? I'm assuming you're using a bunch of temporary
properties? Also you just made adding a step significantly harder, what
if
from building the app, I need to build another layer build for the
router,
for |> I add ONE line of code, for yours I edit the build function in the
right location, and change the build router function to load from a
different property, and build another boilerplate function to build this
new thing, and add another property to hold this new thing... WHOA
that's a
lot more steps, and a MUCH higher risk of a mistake!Again, this is the most outer place of everything. I mean, all
frameworks and applications were and are able to solve these issues
easily, nicely, and testable. Just because my /proposed/ solution to an
example that is constructed in the first place is not as nice as those
solutions does not make the examples better. :P
You didn't address the fact that your "solution" makes it much harder to
modify the steps in place.
$ret = getConfig()
|> buildApp($$)
|> buildRouter($$)
|> parseResponse($$);
Shoot, I forgot to build Dispatcher; Let me add that
$ret = getConfig()
|> buildApp($$)
|> buildRouter($$)
- |> buildDispatcher($$)
|> parseResponse($$);
Such a minimal change. Lets try yours:
final class ResponseBuilder {
private $config;
private $app;
private $router;
private $response;
private function loadConfig() {}
private function buildApp() {
$this->app = new App($this->config);
}
private function buildRouter() {
$this->router = new Router($this->app);
}
private function parseResponse() {
$this->response = new Response($this->router);
}
public function build() {
$this->loadConfig();
$this->buildApp();
$this->buildRouter();
$this->parseResponse();
return $this->response;
}
}
Shoot, I forgot dispatcher; let me add that
final class ResponseBuilder {
private $config;
private $app;
private $router;
-
private $dispatcher;
private $response;private function loadConfig() {}
private function buildApp() {
$this->app = new App($this->config);
}private function buildRouter() {
$this->router = new Router($this->app);
} -
private function buildDispatcher() {
-
$this->dispatcher = new Dispatcher($this->router);
-
}
-
private function parseResponse() {
-
$this->response = new Response($this->router);
-
$this->response = new Response($this->dispatcher);
}
public function build() {
$this->loadConfig();
$this->buildApp();
$this->buildRouter(); -
$this->buildDispatcher();
$this->parseResponse();return $this->response;
}
}
Whoa that's significantly more changes, and therefore significantly more
chances to make a typo!
Again, you're solution is to use OOP, but the pipe operator's purpose is
to
make procedural code cleaner. Not one of your solutions maintained a
procedural interface, and therefore is not an argument against the |>
operator. The purpose of this operator is syntactic sugar, which "is
syntax
within a programming language that is designed to make things easier to
read or to express. It makes the language "sweeter" for human use: things
can be expressed more clearly, more concisely, or in an alternative
style that
some may prefer."[1] (emphasis mine). Its not designed to solve a
problem
of how can I do this thing, but how can I write this thing such that my
team might be able to understand it better.Yes, you can rewrite these examples in an entirely different way, but now
you're not comparing apples to even other fruit (procedural vs OO is not
the same). The point of this is to take a very common pattern (nesting
many
procedural calls) and make it easier to read and manage later.foreach (scandir($arg) as $path) {
if ($path !== '.' && $path !== '..') {
$ret[] = getFileArg($arg .DIRECTORY_SEPARATOR
. $path);
}
}What can I say ...
Valid, and the only way I've seen this written I like. :)
--
Richard "Fleshgrinder" Fussenegger
[1]
https://github.com/symfony/symfony/tree/master/src/Symfony/Component/HttpKernel/Tests
Waiting for what example? There's been plenty of examples showing how
existing options, rewritten with |> are easier. If you disagree, that's an
opinion, other's agree. If people think its better, why not add it for
them? Like every other feature discussed on this list, nothing is forcing
you to use it.
A real world example. :)
Nice to have does not necessarily mean that it should be added because
designing a language is a hard task and providing two or more ways to
achieve something is sometimes good and sometimes bad. I consider this
situation bad. That does not mean that I am right and you are wrong, it
is simply my opinion. I am also not saying that I would never use it if
it is there nor to I feel forced to use it if it is there. I just
believe that it is a bad syntactic sugar that encourages bad programming.
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($app)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);Without the pipe operator, it could look like the following:
$config = loadConfig();
$dic = buildDic(config);
$app = getApp($dic);
$router getRouter($app);
$dispatcher = getDispatcher($router, $request);
$businessResponse = dispatchBusinessLogic($dispatcher, $request, new
Response());
$renderedResponse = renderResponse($businessResponse);
$psr7Response = buildPsr7Response($renderedResponse);
$response = emit($psr7Response);That's a lot of unnecessary assignments, however, and the variable names
don't even provide any additional readability benefits because the function
names are already self-documenting.The first example, with |> is much easier to QUICKLY see what's happening,
because all the function calls are aligned, so I can read quickly top to
bottom knowing what's happening. The second requires much more cognitive
interaction to understand what's going on. Sure you could reformat it to
align all the =, now you have lots of white-space between your variables
and what you're assigning to them... equally as bad in my book.
There are no /unnecessary assignments/ because those assignments happen
anyways even with the pipe operator. However, at least you have explicit
lifetime management.
But that is no my problem with that constructed example, it is the fact
that it assumes that all of those functions exist but there is no
software that defines such functions for those operations. Pretty much
all cool kids are using classes for this kind of stuff. But let us
assume we do it as proposed.
function getGlobals() {
// ...
}
function parseRequest($globals) {
// ...
}
function buildPsr7Request($request) {
// ...
}
function loadConfig() {
// ...
}
function buildDic($config) {
// ...
}
function getApp($dic) {
// ...
}
function getRouter($app) {
// ...
}
function getDispatcher($router, $request) {
// ...
}
function dispatchBusinessLogic($dispatcher, $request, $response) {
// ...
}
function renderResponse($businessResponse) {
// ...
}
function buildPsr7Response($renderedResponse) {
// ...
}
function emit($psr7Response) {
// ...
}
$request = getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($$)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);
Suddenly I magically need to know how all of that procedural stuff goes
together just to be able to keep it short while calling all of it?!?
I hope you understand now why I am still asking for real world examples
that are not based on endlessly bad code.
None, I don't write Symfony apps, however they wrote quite a few [1].
I was explicitly asking for userland Kernel and not for the base class
that Symfony provides.
A rather extreme example of such a /most outer/ but you know how it is
with constructed examples:http://www.yegor256.com/2014/10/03/di-containers-are-evil.html#the-right-way
GROSS. This would not be allowed any where I do code reviews.
What would be your answer if the author asks /why?/
You didn't address the fact that your "solution" makes it much harder to
modify the steps in place.$ret = getConfig()
|> buildApp($$)
|> buildRouter($$)
|> parseResponse($$);Shoot, I forgot to build Dispatcher; Let me add that
$ret = getConfig()
|> buildApp($$)
|> buildRouter($$)
- |> buildDispatcher($$)
|> parseResponse($$);Such a minimal change. Lets try yours:
final class ResponseBuilder {
private $config;
private $app;
private $router;
private $response;private function loadConfig() {} private function buildApp() { $this->app = new App($this->config); } private function buildRouter() { $this->router = new Router($this->app); } private function parseResponse() { $this->response = new Response($this->router); } public function build() { $this->loadConfig(); $this->buildApp(); $this->buildRouter(); $this->parseResponse(); return $this->response; }
}
Shoot, I forgot dispatcher; let me add that
final class ResponseBuilder {
private $config;
private $app;
private $router;
private $dispatcher;
private $response;private function loadConfig() {}
private function buildApp() {
$this->app = new App($this->config);
}private function buildRouter() {
$this->router = new Router($this->app);
}private function buildDispatcher() {
$this->dispatcher = new Dispatcher($this->router);
}
private function parseResponse() {
$this->response = new Response($this->router);
$this->response = new Response($this->dispatcher);
}
public function build() {
$this->loadConfig();
$this->buildApp();
$this->buildRouter();$this->buildDispatcher();
$this->parseResponse();return $this->response;
}
}Whoa that's significantly more changes, and therefore significantly more
chances to make a typo!
At least the complexity is not completely hidden behind some arbitrary
procedural function call. You assume that the procedural function that
you are introducing already exists and returns the correct thingy that
continues the chain in exactly the order you want it too. Those are a
lot of assumptions considering the fact that the pipe operator is meant
for combining arbitrary libraries.
Sorry, not convinced but a real world example might change that. ;)
--
Richard "Fleshgrinder" Fussenegger
Hi,
I agree they are bad APIs.
Framework/library developer should provide better API
$request = getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);
should be
require('some.lib');
$request = getRequest();
/* where getRequest() is
function getRequest() {
return getGlobals()
|> parseRequest($$)
|> buildPsr7Request($$);
}
*/
$response = loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($$)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);
should be
require('some.lib');
$request = getRequest();
$response = getResponse($request);
/* where getResponse() is
function getRequest($request) {
return loadConfig()
|> buildDic($$)
|> getApp($$)
|> getRouter($$)
|> getDispatcher($$, $request)
|> dispatchBusinessLogic($$, $request, new Response())
|> renderResponse($$)
|> buildPsr7Response($$)
|> emit($$);
}
*/
Suddenly I magically need to know how all of that procedural stuff goes
together just to be able to keep it short while calling all of it?!?I hope you understand now why I am still asking for real world examples
that are not based on endlessly bad code.
"|>" is just a building block for simpler coding. It could be used badly, but
it helps a lot. Procedural code could be much simpler and readable with "|>".
function getRequest() {
$config = loadConfig();
$dic = buildDic($config);
$app = getApp($dic);
$route = getRouter($app);
$dispatcher = getDispatcher($route, $request);
$businessLogic = dispatchBusinessLogic($dispatcher, $request, new
Response());
$resp = renderResponse($businessLogic);
$response = buildPsr7Response($resp);
return emit($response);
}
"|>" version is much easier to read and write.
I suppose most users will use it for easier coding, not for opposite.
Regards,
--
Yasuo Ohgaki
yohgaki@ohgaki.net
Hi!
"|>" is just a building block for simpler coding. It could be used badly, but
it helps a lot. Procedural code could be much simpler and readable with "|>".
I don't see how it helps anything. It just replaces clear variable names
with cryptic sequences of characters with no intuitive meaning and magic
semantics invented solely to save a few keystrokes. Moreover, it would
only in one sole use case where functions always return a value that is
immediately passed to the next one and is sole argument for it, and
never produce errors or require any logic beyond calling them in a
sequence on one value.
"|>" version is much easier to read and write.
Quite the opposite. It has completely unobvious syntax (what is $$? What
is the value of $$? how I see this value if I need to debug this code?)
and does not allow to do anything but making code more cryptic.
Stas Malyshev
smalyshev@gmail.com
Hi!
"|>" is just a building block for simpler coding. It could be used
badly, but
it helps a lot. Procedural code could be much simpler and readable with
"|>".I don't see how it helps anything. It just replaces clear variable names
with cryptic sequences of characters with no intuitive meaning and magic
semantics invented solely to save a few keystrokes. Moreover, it would
only in one sole use case where functions always return a value that is
immediately passed to the next one and is sole argument for it, and
never produce errors or require any logic beyond calling them in a
sequence on one value.
That seems to be a misrepresentation of the proposal - the $$ is needed
because there might be other parameters. As for sole use case - there are
many pieces of code that manipulate the return values of functions, step by
step.
"|>" version is much easier to read and write.
Quite the opposite. It has completely unobvious syntax (what is $$? What
is the value of $$? how I see this value if I need to debug this code?)
and does not allow to do anything but making code more cryptic.
I have the feeling that if everyone involved explicitly prefixed their
opinions with "I think that", this would be a better and more fruitful
discussion. You think the syntax completely unobvious - that doesn't make
it so. Clearly others find it much easier to read.
Regards
Peter
--
CV: careers.stackoverflow.com/peterlind
LinkedIn: plind
Twitter: kafe15
Hi!
I have the feeling that if everyone involved explicitly prefixed their
opinions with "I think that", this would be a better and more fruitful
Is there any other option?
discussion. You think the syntax completely unobvious - that doesn't
make it so. Clearly others find it much easier to read.
If you think it's obvious - it also doesn't make it so, then. It appears
we have no way to determine if it's obvious or not. But I'd like to
venture a proposal - find somebody who is not familiar with this
proposal and maybe even with PHP and ask them - what do you think * or +
or -> may mean in PHP? If they are familiar with any other languages
like PHP, they would venture a guess with would be pretty close to the
truth. Now ask them what |> and $$ may mean. Are you ready to tell me
their answers would be as accurate and close to the truth as before?
Stas Malyshev
smalyshev@gmail.com
Hi!
I have the feeling that if everyone involved explicitly prefixed their
opinions with "I think that", this would be a better and more fruitfulIs there any other option?
As in "better options"? I don't think so. As in "could my statement be
taken any other way"? Clearly, as the way you put it forward you were
claiming not that you though things unobvious, but that they were
unobvious. You are trying to make the exact same argument, in different
words, in your argument below.
discussion. You think the syntax completely unobvious - that doesn't
make it so. Clearly others find it much easier to read.If you think it's obvious - it also doesn't make it so, then.
That is a logical conclusion, yes. I didn't state my opinion on the pipe
operator, though.
It appears
we have no way to determine if it's obvious or not. But I'd like to
venture a proposal - find somebody who is not familiar with this
proposal and maybe even with PHP and ask them - what do you think * or +
or -> may mean in PHP? If they are familiar with any other languages
like PHP, they would venture a guess with would be pretty close to the
truth. Now ask them what |> and $$ may mean. Are you ready to tell me
their answers would be as accurate and close to the truth as before?
Are you willing to argue that PHP should never implement features not found
commonly in other languages? Because that's where you're going with that
argument.
Regards
Peter
--
CV: careers.stackoverflow.com/peterlind
LinkedIn: plind
Twitter: kafe15
Hi!
Are you willing to argue that PHP should never implement features not
found commonly in other languages? Because that's where you're going
with that argument.
No, I'm not and no, I'm not. But I do claim it is unobvious - though
that alone would not be the reason to reject it, but I outlined many
others in my previous emails - and given that you did not contradict my
argument on that but instead argued along the lines of "if it's
unobvious it's still OK to implement it" - you agree that it is so, or
at least can't offer any argument to the contrary.
Stas Malyshev
smalyshev@gmail.com
I don't see how it helps anything. It just replaces clear variable names
with cryptic sequences of characters with no intuitive meaning and magic
semantics invented solely to save a few keystrokes.
Yep, that's exactly what "->" does. It's just pointless syntactic
sugar for hiding "$obj" when calling functions which happen to take an
instance as their magicly passed argument.
Oh, sorry, we were talking about the function version of ->, my
mistake. That's a totally different thing.
Moreover, it would
only in one sole use case where functions always return a value that is
immediately passed to the next one and is sole argument for it,
Hrmmm... Did you read the RFC? It's pretty clear that the lhs
expression can be used in a lot more cases that function calls and
that it needn't be the only argument. Better read it again.
Quite the opposite. It has completely unobvious syntax (what is $$? What
is the value of $$? how I see this value if I need to debug this code?)
and does not allow to do anything but making code more cryptic.
That's a great reason for voting against short ternary syntax. What
the hell is "?:", anyway?
-Sara
Hi!
Yep, that's exactly what "->" does. It's just pointless syntactic
No, not really. Calling method on an object is an universally accepted
phrase in many languages. True, not all of them use ->, and for some of
them -> may have other meaning, but if you tell somebody at least
vaguely familiar with OOP "-> is a method call operator", you're done
teaching them about ->. That assuming they don't come from numerous
languages where -> is the method call operator.
Moreover, there's no easy and more readable way to call methods in PHP,
so -> is the best way to go.
Moreover, calling methods is a very frequent operation, and any time you
need it, regardless of what is the circumstance, you'd use ->
Neither of these is true for |>-$$ thing - it does not have any matches
in any languages I can think of (maybe Perl 6 has something like that
because what doesn't it have?), it captures only one particular case and
it does something much better done the existing way.
Oh, sorry, we were talking about the function version of ->, my
mistake. That's a totally different thing.
I'm sorry I didn't get what you meant here. "function version of ->"
makes no sense to me - function version of -> is the function call itself.
Hrmmm... Did you read the RFC? It's pretty clear that the lhs
expression can be used in a lot more cases that function calls and
that it needn't be the only argument. Better read it again.
Only one argument can be $$. So if you need to call two functions and
pass the result of them to the third, the magic does not work anymore.
If you need to do anything on any of these arguments, magic does not
work anymore. If you need to do any error checking or branching, the
magic does not work anymore. Did I misunderstand the RFC? Do we have $1$
and $2$ coming? Maybe, but I found no mention of it in the RFC.
That's a great reason for voting against short ternary syntax. What
the hell is "?:", anyway?
That would be a good question (and there are a number of languages that
omit ?: for exactly that reason) had C, C++ and Perl not existed and
were not direct predecessors and influences on PHP. For the full list of
languages that know what the hell is "?:" please see:
https://en.wikipedia.org/wiki/%3F:
Now, how long is the list of languages which know what is |>, Hack
excluded? I suspect the answer is "not very". Probably not 20+ long?
Stas Malyshev
smalyshev@gmail.com
Yep, that's exactly what "->" does. It's just pointless syntactic
No, not really. Calling method on an object is an universally accepted
phrase in many languages.
I never said it wasn't. I said it was pointless syntactic sugar.
Moreover, there's no easy and more readable way to call methods in PHP,
so -> is the best way to go.
But there are ways to implement "oop" with explicit data passage.
Ways which could have been leveraged in PHP3. We don't use those ways
because it's a PITA and produces unreadable code. Which is what
syntactic sugar is meant to alleviate.
Moreover, calling methods is a very frequent operation, and any time you
need it, regardless of what is the circumstance, you'd use ->
Calling functions, chaining the output of one into the next is also a
frequent argument.
Neither of these is true for |>-$$ thing - it does not have any matches
in any languages I can think of.
You lack imagination. Here's three to get you started:
Elixir: http://elixir-lang.org/getting-started/enumerables-and-streams.html#the-pipe-operator
F#: https://msdn.microsoft.com/en-us/library/dd233229.aspx#Anchor_11
Clojure: https://clojuredocs.org/clojure.core/-%3E
And if they seem to obscure, how about this proposal for Javascript?
https://github.com/mindeavor/es-pipeline-operator
Oh, sorry, we were talking about the function version of ->, my
mistake. That's a totally different thing.I'm sorry I didn't get what you meant here. "function version of ->"
makes no sense to me - function version of -> is the function call itself.
$foo->bar() calls the bar() method invisibly passing $foo as its
similarly hidden $this argument.
$foo |> bar($$) calls the bar() method quite visible passing $foo as
its explicitly non-hidden first argument.
The pipe call is clear and easily traces, the object call requires
understanding OOP. I'll grant that OOP is much more common, and
therefore generally understood by individuals who already know OOP (as
tautologous as that statement is).
-Sara
Hi!
You lack imagination. Here's three to get you started:
Elixir: http://elixir-lang.org/getting-started/enumerables-and-streams.html#the-pipe-operator
F#: https://msdn.microsoft.com/en-us/library/dd233229.aspx#Anchor_11
Clojure: https://clojuredocs.org/clojure.core/-%3EAnd if they seem to obscure, how about this proposal for Javascript?
https://github.com/mindeavor/es-pipeline-operator
Thanks, now I know where it comes from. This would be useful to mention
in the RFC. Note the interesting difference - none of them has $$. This
construct seems to be much more natural in functional languages (which
all of the above are) due to the fact that functions there are more
designed for combining, currying, being composed, operated on, etc.
$foo->bar() calls the bar() method invisibly passing $foo as its
Explicitly calling method bar on $foo seems to be strange definition of
"invisibly". $this (in all it's forms) is a basic concept of OOP.
$foo |> bar($$) calls the bar() method quite visible passing $foo as
its explicitly non-hidden first argument.
Visible passing is bar($foo). We don't need another syntax for it. For
|> it's not passing of $foo - it's passing of $$. Which needs to be a)
figured out what that magic $$ means (note that none of the languages
above have magic variables there) and b) located where the value for
magic $$ comes from.
The pipe call is clear and easily traces, the object call requires
understanding OOP. I'll grant that OOP is much more common, and
therefore generally understood by individuals who already know OOP (as
tautologous as that statement is).
The problem is that, as I see it, pipe call doesn't easily trace,
especially with addition of $$. As I mentioned already, this code also
would be hard to debug.
Stas Malyshev
smalyshev@gmail.com
Neither of these is true for |>-$$ thing - it does not have any matches
in any languages I can think of.You lack imagination. Here's three to get you started:
Elixir: http://elixir-lang.org/getting-started/enumerables-and-streams.html#the-pipe-operator
F#: https://msdn.microsoft.com/en-us/library/dd233229.aspx#Anchor_11
Clojure: https://clojuredocs.org/clojure.core/-%3EAnd if they seem to obscure, how about this proposal for Javascript?
https://github.com/mindeavor/es-pipeline-operator
I am really, truly sorry as this is a serious matter, but I kinda
laughed a little bit at this burn...
|> seems like a common symbol to use, but it admittedly does look a
little strange. Hmm? Do I have a better proposal? Nope, not really. It's
all just syntax in the end.
--
Stephen
Hi!
|> seems like a common symbol to use, but it admittedly does look a
So, usage in one semi-obscure language (F#) and one completely obscure
one (Elixir) - Clojure doesn't use |> - and one proposal for Javascript
now qualifies for "common". And that counting the fact that neither of
them actually uses the worst part of proposed syntax - magic variable $$.
Stas Malyshev
smalyshev@gmail.com
Hi!
|> seems like a common symbol to use, but it admittedly does look a
So, usage in one semi-obscure language (F#) and one completely obscure
one (Elixir) - Clojure doesn't use |> - and one proposal for Javascript
now qualifies for "common". And that counting the fact that neither of
them actually uses the worst part of proposed syntax - magic variable $$.Stas Malyshev
smalyshev@gmail.com
If the issue is $$ feels too Perl like, what is the alternative? Is
there another way to chain methods cleanly?
In a sense, what we're really talking about here is continuations.
Continuations (over-simplified) are a clean way of setting up "run this
function, pass its result to this function, pass its result to this
function, etc." That makes composition really easy. |> is essentially
a continuation syntax. The $$ is to work around the fact that PHP
function can have an arbitrary number of parameters, whereas
continuations work best with single-parameter functions.
Of course, with currying any multi-parameter function can be reduced to
a series of single parameter functions. So what if we were to limit the
concurrency syntax to single-parameter functions? And if you want to
reduce a multi-parameter function to a single parameter function, yay
closures.
Would that limitation help or hinder?
Either way, I firmly believe that more functional-friendly capabilities
like continuations, promises, etc. are a direction that PHP needs to
move, and syntax in that direction is valuable.
Hi!
If the issue is $$ feels too Perl like, what is the alternative? Is
there another way to chain methods cleanly?
Well, that's a loaded question. It presumes we already agreed on needing
to chain methods using special syntax outlined in the RFC - which we
didn't - and the only problem is whether it's $$ or $* or @% or some
other sequence of symbols. I'm not convinced at all of the premise, so
discussing which exactly two characters should be used for $$ does not
really appeal to me.
In a sense, what we're really talking about here is continuations.
Not sure how it's continuations:
https://en.wikipedia.org/wiki/Continuation
If anything, generators look like continuations. This just looks like
fancy way to write sequence of function calls with novel syntax. Do you
mean something else by "continuations"? Or I missed some part of the RFC?
a series of single parameter functions. So what if we were to limit the
concurrency syntax to single-parameter functions? And if you want to
reduce a multi-parameter function to a single parameter function, yay
closures.
I'm not sure where "concurrency syntax" comes from. What's concurrent here?
Either way, I firmly believe that more functional-friendly capabilities
like continuations, promises, etc. are a direction that PHP needs to
move, and syntax in that direction is valuable.
I'm not sure that's where PHP needs to move, but we already have
continuations, as I mentioned. As for other async capabilities like
promises/futures, that may be possible but there are some hard questions
need to be answered here because PHP is not well suited for environment
sharing.
I also am not sure functional syntax is well suited for PHP, given that
PHP is not a functional language and vast majority on PHP code is not
written in functional manner. Functional programming requires quite
different approach than most PHP applications take, and frankly I'm not
sure why this should change - there are enough functional languages around.
Of course, that doesn't mean avoiding all functional features - some
of them make total sense even in non-functional language, like closures
or functions like map/reduce/filter. Some, however, are harder to fit.
Stas Malyshev
smalyshev@gmail.com
On Thu, May 12, 2016 at 1:19 AM, Larry Garfield larry@garfieldtech.com
wrote:
Hi!
|> seems like a common symbol to use, but it admittedly does look a
So, usage in one semi-obscure language (F#) and one completely obscure
one (Elixir) - Clojure doesn't use |> - and one proposal for Javascript
now qualifies for "common". And that counting the fact that neither of
them actually uses the worst part of proposed syntax - magic variable $$.Stas Malyshev
smalyshev@gmail.comIf the issue is $$ feels too Perl like, what is the alternative? Is
there another way to chain methods cleanly?In a sense, what we're really talking about here is continuations.
Continuations (over-simplified) are a clean way of setting up "run this
function, pass its result to this function, pass its result to this
function, etc." That makes composition really easy. |> is essentially
a continuation syntax. The $$ is to work around the fact that PHP
function can have an arbitrary number of parameters, whereas
continuations work best with single-parameter functions.Of course, with currying any multi-parameter function can be reduced to
a series of single parameter functions. So what if we were to limit the
concurrency syntax to single-parameter functions? And if you want to
reduce a multi-parameter function to a single parameter function, yay
closures.Would that limitation help or hinder?
Either way, I firmly believe that more functional-friendly capabilities
like continuations, promises, etc. are a direction that PHP needs to
move, and syntax in that direction is valuable.
I wonder if it's possible to use $$ only when it's necessary to pass in the
argument positionally, that is, if it's not used on the right hand side,
it's passed in magically as the first argument, otherwise it's passed in
wherever the $$ (or whatever other placeholder you want) is used.
OR we just prepend it to the argument list used on the RHS. I noticed
that Closure also have ->> for passing in the argument last, vs -> for
first. I'm not sure I would like to have |>> for example.
I think that $$ as a positional placeholder gives us the most flexibility.
$$ is super easy to type, apparently is possible with little conflict to
existing syntax, etc.
I'm +1 on |> and $$.
- Davey
On Thu, May 12, 2016 at 1:19 AM, Larry Garfield larry@garfieldtech.com
wrote:Hi!
|> seems like a common symbol to use, but it admittedly does look a
So, usage in one semi-obscure language (F#) and one completely obscure
one (Elixir) - Clojure doesn't use |> - and one proposal for Javascript
now qualifies for "common". And that counting the fact that neither of
them actually uses the worst part of proposed syntax - magic variable
$$.
Is $0 being considered? It's not ideal but is used as "pattern match
reference" in preg_replace, so we have a (sort of) precedent here. Same for
$1 (first capturing subpattern).
--
Stas Malyshev
smalyshev@gmail.comIf the issue is $$ feels too Perl like, what is the alternative? Is
there another way to chain methods cleanly?In a sense, what we're really talking about here is continuations.
Continuations (over-simplified) are a clean way of setting up "run this
function, pass its result to this function, pass its result to this
function, etc." That makes composition really easy. |> is essentially
a continuation syntax. The $$ is to work around the fact that PHP
function can have an arbitrary number of parameters, whereas
continuations work best with single-parameter functions.Of course, with currying any multi-parameter function can be reduced to
a series of single parameter functions. So what if we were to limit the
concurrency syntax to single-parameter functions? And if you want to
reduce a multi-parameter function to a single parameter function, yay
closures.Would that limitation help or hinder?
Either way, I firmly believe that more functional-friendly capabilities
like continuations, promises, etc. are a direction that PHP needs to
move, and syntax in that direction is valuable.I wonder if it's possible to use $$ only when it's necessary to pass in the
argument positionally, that is, if it's not used on the right hand side,
it's passed in magically as the first argument, otherwise it's passed in
wherever the $$ (or whatever other placeholder you want) is used.OR we just prepend it to the argument list used on the RHS. I noticed
that Closure also have ->> for passing in the argument last, vs -> for
first. I'm not sure I would like to have |>> for example.I think that $$ as a positional placeholder gives us the most flexibility.
$$ is super easy to type, apparently is possible with little conflict to
existing syntax, etc.I'm +1 on |> and $$.
- Davey
Is $0 being considered? It's not ideal but is used as "pattern match
reference" in preg_replace, so we have a (sort of) precedent here. Same for
$1 (first capturing subpattern).
I don't believe it's been brought up yet, and it'd actually resolve
the minor conflict with the variable-variable-comment-brace pattern,
but the objections I've seen seem to have less to do with symbol
choice and more to do with blanket dislike for the idea as a whole.
That said, I'm not over-concerned with what the symbol is, provided it
makes sense within the scope of PHP, and doesn't cause too many
headaches in the frontend compiler.
-Sara
Hi Larry,
Hi!
|> seems like a common symbol to use, but it admittedly does look a
So, usage in one semi-obscure language (F#) and one completely obscure
one (Elixir) - Clojure doesn't use |> - and one proposal for Javascript
now qualifies for "common". And that counting the fact that neither of
them actually uses the worst part of proposed syntax - magic variable $$.Stas Malyshev
smalyshev@gmail.comIf the issue is $$ feels too Perl like, what is the alternative? Is
there another way to chain methods cleanly?
$$ looks like Perl, I agree.
However, we will get used to it soon. Namespace separator "" seemed
strange to me, but I was used to it after a while. It would be more
beneficial to users if we adopt the same symbol as Hack. IMO.
Regards,
--
Yasuo Ohgaki
yohgaki@ohgaki.net
Doesn't Nikic's scalar objects (https://github.com/nikic/scalar_objects)
more or less achieve the same thing while also cleaning up the std lib?$ret = scandir($arg)
->filter(function(){})
->map(function(){})
->merge($someOtherArray);
This type of solution is only applicable if the operations to be chained
can be bundled with the definition of the type itself, and are generic
enough that they should be.
Consider the pipe between scandir()
and array_filter(...). Suppose I
already have a function or method removeDots(array $paths):array which I
would like to use:
$ret = scandir($arg)
|> $this->removeDots($$)
|> ...
If the solution is method chaining, then either removeDots() has to be
added to PHP's built-in array type, or scandir()
must return a specific
class (eg FileList) which has such a method, instead of a simple array.
Either way, the author of either scandir()
or of the "array" type must
predict what operations the user is going to want to chain. That isn't
knowledge they necessarily have, especially since the operations could be
entirely specific to their use case.
With the pipe operator, all three of the left hand side, right hand side
and the type flowing between them can belong to different codebases.
Could you use a closure instead to accomplish this? (Again yes, Sara could
you clarify if this is permitted?)$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..';
})
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$$)
|> (function($fileList) {
if (someCheck($fileList)) {
something();
}
return $fileList;
})($$)
|> getFileArg($$)
|> array_merge($ret, $$);
You could, but IMO you lose a lot of the elegance if you go that
route. This syntax works best when you have a series of functions
which /don't/ have failure cases (and many many functions don't), or
who fail via exceptions.
Using current syntax, the above would look like:
$ret = array_merge($ret, getFileArg(function($fileList) {
if (someCheck($fileList)) { something(); }
return $fileList;
})(array_map(function($x) use ($arg) { return $arg . '/' . $x; },
array_filter(scandir($arg), function($x) { return $x !== '.' &&
$x !== '..'; }))
)));
It's way more readable in the pipe syntax version, but it's
overloading a single statement in both places.
-Sara
It's way more readable in the pipe syntax version, but it's
overloading a single statement in both places.
The rop examples show another style of working BUT it only really
changes the way of ordering SOME elements of the code. And the pipe
operator is only really of use in SOME areas of code? Other changes to
the code base could achieve the same results, such as creating the
'improved' array handling API that people keep calling for? I accept
that parameter ordering on some functions is not the best for
streamlining some steps, but even pipe chaining has it's restrictions
and so again is not a total solution, simply another 'complication' when
trying to read code.
I still think this fails to understand the complexity it adds to
handling changes to the code as/if other variations of intermediate code
handling ARE required. As I said previously, pulling PDO chaining apart
tends to be the first step to correct code that is ignoring intermediate
errors, and simply trying to 'chain' all of the errors together via
exceptions does not work ... as demonstrated in the rop demonstrations
which HAVE to break out exceptions to make the flow work.
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Stephen Coakley wrote on 03/05/2016 01:57:
The basic pattern would be:
|=> $tempVar; // terminate the chain and capture the value
// do stuff with $tempVar
$tempVar // restart the chainSo:
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x !=
'..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x;
}, $$)
|=> $fileList;
if ( someCheck($fileList) {
something();
}
$fileList
|> getFileArg($$)
|> array_merge($ret, $$)
|=> $ret;If I don't need the condition any more, I can delete lines 4 to 8, and
I've got back my original chain.Could you use a closure instead to accomplish this? (Again yes, Sara
could you clarify if this is permitted?)$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x !=
'..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' .
$x; }, $$)
|> (function($fileList) {
if (someCheck($fileList)) {
something();
}
return $fileList;
})($$)
|> getFileArg($$)
|> array_merge($ret, $$);Not completely the best, but perhaps there's some sort of an idea here?
The disadvantage here is that you're still having to mutate your code to
fit the piped style, even if it's just wrapping into the closure. I was
aiming for a style that would let me freely mix piped and unpiped code.
Meanwhile, I still see having the assignment at the end of the chain as
an end in itself - the ability to use the pipe operator as an expression
seems like a nightmare waiting to happen to me. As currently proposed,
you could write this, which is just horrible:
if (
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x !=
'..'; })
|> count($$)
>
scandir($arg)
|> count($$)
) { ... }
Obviously, it's always possible to show horrible abuses of a feature,
but I can't think of any sensible place to use a pipe as an expression
except in order to either assign it to a variable, or return it from a
function.
Regards,
Rowan Collins
[IMSoP]
I am very much in favour of this. I would typically write the example with
an intermediate variable to avoid all the sad nesting.
$files = scandir($arg);
$files = array_filter($files, function ($x) { return $x !== '.' && $x !==
'..'; });
$files = array_map(function ($x) use ($arg) { return $arg . '/' . $x; },
$files);
$files = getFileArg($files);
$ret = array_merge($ret, $files);
But it's certainly unfortunate that I had to revert from expressions
without state to ordered statements mutating a variable just to avoid
deeply nested parens. Anything that enables more code to be written
declaratively rather than imperatively is a win in my opinion, especially
if it can be implemented as mere syntactic sugar as is the case here.
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
Hi Sara,
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
PHP is not pure OO anyway. This proposal simplifies procedural
programming a lot. Although abuse would be possible, code could be
more readable and cleaner with this.
+1
Regards,
--
Yasuo Ohgaki
yohgaki@ohgaki.net
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
I've added a summary (as best I see it) of third-party arguments.
Others should feel free to edit the TPA section at will to rephrase or
add to the arguments on either side (the side you support, obviously).
I've also changed the "Targetting version" section from 7.Next to
explicitly be 7.2 as 7.1 is far too close to try to push a
controversial feature into. We have time to argue... :)
-Sara
Hi Sara,
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
After I first read the rfc my immediate feeling was negative. Yet another
syntaxic sugar that will be used so rarely that I will have to try&fail and
rtfm when I see it.
The cases presented in this thread and in other discussions changed my
mind. I will vote for it.
Thanks
Pierre
Not sure if it's already been mentioned, but I've noticed this operator
would be useful when you want to modify something but need to transform it
before and after.
For example, I have a class that serializes/deserializes arrays of strings,
and I need to modify the array that it has encoded. The code currently
looks like this:
$id = StringList::encode(array_replace(StringList::decode($id), $replace));
The parallel between the encode and decode can be made more obvious by
writing it as a sequence of steps:
$id = $id
|> StringList::decode($$)
|> array_replace($$, $replace)
|> StringList::encode($$);
Neat. A similar example is when you need to modify a percentage as a factor:
$percent = $this->modifyFactor($percent / 100, $this->moreParams(),
Blah::moreParams()) * 100;
This could, arguably, be better written better as:
$percent = $percent
|> $$ / 100
|> $this->modifyFactor($$, $this->moreParams(), Blah::moreParams())
|> $$ * 100;
This is one of my favorites out of HackLang. It's pure syntactic
sugar, but it goes a long way towards improving readability.
https://wiki.php.net/rfc/pipe-operator
$id = $id
|> StringList::decode($$)
|> array_replace($$, $replace)
|> StringList::encode($$);
To reiterate my comments from earlier in the thread, I think the "$id =
$id" looks really weird here, and spoils the step-by-step layout - and,
in this case, the symmetry.
I would like to be able to write it as:
$id
|> StringList::decode($$)
|> array_replace($$, $replace)
|> StringList::encode($$)
|>= $id;
Or just:
$id
|> StringList::decode($$)
|> array_replace($$, $replace)
|> StringList::encode($$)
|> $id;
Sara, what do you think of this tweak, and my subsequent suggestion that
the pipe should only be usable as a statement, not an expression?
Regards,
Rowan Collins
[IMSoP]
On Tue, May 17, 2016 at 9:14 PM, Rowan Collins rowan.collins@gmail.com
wrote:
To reiterate my comments from earlier in the thread, I think the "$id =
$id" looks really weird here, and spoils the step-by-step layout - and, in
this case, the symmetry.
We have the same thing already when chaining methods on value/immutable
objects, like with PSR-7:
$message = $message
->withHeader('foo', 'bar')
->withAddedHeader('foo', 'baz');
So depending on often that pattern is used, many are probably already used
to seeing "$var = $var ....", myself included.
On Tue, May 17, 2016 at 9:14 PM, Rowan Collins <rowan.collins@gmail.com
mailto:rowan.collins@gmail.com> wrote:To reiterate my comments from earlier in the thread, I think the "$id = $id" looks really weird here, and spoils the step-by-step layout - and, in this case, the symmetry.
We have the same thing already when chaining methods on value/immutable
objects, like with PSR-7:$message = $message ->withHeader('foo', 'bar') ->withAddedHeader('foo', 'baz');
So depending on often that pattern is used, many are probably already
used to seeing "$var = $var ....", myself included.
A fair point. Although you could of course combine OO chaining with
syntax chaining:
$message
->withHeader('foo', 'bar')
->withAddedHeader('foo', 'baz')
|=> $message;
The longer the chain, the more awkward it is to rewind to the beginning
to find where the result is being used / saved.
See also my earlier post where I went into why a tail assignment would
improve the ability to debug pipes, and mix them with non-piped code:
http://marc.info/?l=php-internals&m=146205812523026&w=2
Again, all of this applies to mixing with existing OO pipes like PSR-7.
Regards,
Rowan Collins
[IMSoP]
$id
|> StringList::decode($$)
|> array_replace($$, $replace)
|> StringList::encode($$)
|> $id;Sara, what do you think of this tweak, and my subsequent suggestion that the
pipe should only be usable as a statement, not an expression?
I'm not strictly against it, but I'm not a huge fan. Primarily, it's
the statement-only restriction that bothers me since (apart from
control flow structures), most constructs in PHP are expressions, not
statements, and this feature naturally fits in the composable
expressions category, not the top-level statement category. From an
implementation standpoint, I'm fairly confident that the patch would
be much more complex as a result of enforcing that statement-only
requirement.
Meanwhile, if we remove the statement-only requirement, then using
"simple-variable" as a signal to switch to expression termination,
then we introduce a whole class of bug where a user fat-fingers the $$
token in what SHOULD be another link in a chain expression, but it's
not detectable as an error because a simple-var is valid.
And one more counter-example:
$x
|> foo($$)
|> $y[$$];
This is no longer a simple var, should that terminate our statement by
writing to $y[$$]? Or should that be seen as the next link in a chain
where we read the value from $y[$$] (which could well have getter side
effects)? I would probably expect the latter, but maybe the former
was intended?
So yeah, I'm really not a fan. That said, if enough other people
chime in that they think these issues are surmountable in "a PHP way",
then I'll take a crack at a PR and we can include it in the
discussion, but as-is.... I can't quite get behind it.
-Sara
On Tue, May 17, 2016 at 4:14 AM, Rowan Collins > rowan.collins@gmail.com wrote: >> On 17/05/2016 11:55, Jesse
Schalken wrote: $id |> >> StringList::decode($$) |> array_replace($$,
$replace) |> >> StringList::encode($$) |> $id; >> >> >> Sara, what do
you think of this tweak, and my subsequent suggestion >> that the pipe
should only be usable as a statement, not an >> expression? >> > I'm
not strictly against it, but I'm not a huge fan. Primarily, > it's the
statement-only restriction that bothers me since (apart > from control
flow structures), most constructs in PHP are > expressions, not
statements, and this feature naturally fits in the > composable
expressions category, not the top-level statement > category.
A perfectly reasonable position. I guess my counter-argument would be
that this is a kind of control flow, a "do-this and-then do-this". But
mostly the suggestion to restrict was to have one right way of laying
things out, rather than allowing people to make it even harder to follow
by mixing nesting with pipes, for instance.
From an implementation standpoint, I'm fairly confident that the > patch would be much more complex as a result of enforcing that >
statement-only requirement.
Ah, I did wonder if that would be the case, and I don't think it would
be worth pushing in that complexity.
Meanwhile, if we remove the statement-only requirement, then using > "simple-variable" as a signal to switch to expression termination, >
then we introduce a whole class of bug where a user fat-fingers the > $$
token in what SHOULD be another link in a chain expression, but > it's
not detectable as an error because a simple-var is valid. > > And one
more counter-example: > > $x |> foo($$) |> $y[$$];
My original suggestion was to have a different token for the terminating
assignment, such as "|=>" (looks a bit prettier than "|>="). That would
get rid of any such ambiguity.
If we keep the pipe as an expression, I guess the assignment can return
its value like a plain "=" would; so:
$foo
|> operation($$)
|=> $foo;
would be extra sugar for:
$foo
|> operation($$)
|> $foo = $$;
Incidentally, the RFC would benefit from a couple of examples of using
something other than a function call as the RHS of the "|> "
And you could even use it mid-pipe, as a kind of "tee":
buildRequest() // (basically the first part of the previous example here)
|> validate($$)
|> convertToCommand($$)
|=> $command
|> execute($$)
|> convertToViewModel($$)
|> render($$)
|> convertToHttpResponse($$)
|> emit($$)
|=> $response;
$command
|> censorCommand($$)
|> logEvent($$);
The other special case that's trickier to incorporate if the pipe acts
as an expression is returning from a function. I presume "return $$"
would be an error, unless it's given special status, which is a shame...
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$)
|> return $$
You mention in the RFC that the source of the data going through the
chain is "clear and unambiguous"; I think it would be great if the
destination of the data were equally clear. For instance, a subtle
edit to your example:
($ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$))
|> getFileArg($$)
|> array_merge($ret, $$)
|> $output->append($$);
At a glance, what would you expect in $ret after that code runs?
I guess this falls close to the "stopping people writing horrible code"
fallacy, but what I'm interested in is what the good code should look like:
scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|=> $ret
|> getFileArg($$)
|> array_merge($ret, $$)
|> $output->append($$);
Regards,
--
Rowan Collins
[IMSoP]