[RFC] Pipe Operator

php.internals

Sara Golemon

10 years ago
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

Bob Weinand

10 years ago
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

Larry Garfield

10 years ago
On 04/29/2016 06:37 PM, Bob Weinand wrote:
> 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

Dan Ackroyd

10 years ago
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

Lester Caine

10 years ago
On 01/05/16 00:20, Dan Ackroyd wrote:
> 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

Côme Chilliet

10 years ago
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.

Unnamed Person

10 years ago
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:

Sebastian Bergmann

10 years ago
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.

Stas Malyshev

10 years ago
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

Marco Pivetta

10 years ago
I would say that this makes the entire functional approach: 1. more readable 2. 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 http://twitter.com/Ocramius http://ocramius.github.com/ On 30 April 2016 at 02:19, Stanislav Malyshev <smalyshev@gmail.com> wrote:

Thomas Punt

10 years ago
Hi! > From: ocramius@gmail.com > > I would say that this makes the entire functional approach: > > 1. more readable > 2. 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 I have to agree with this. The examples in the RFC probably don't show the best examples of it - the pipe operator really clarifies code when nesting numerous simple function calls. Quoting the example above: $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. More likely, the above code would be written as numerous nested function calls: $dispatcher = getDispatcher(getRouter(getApp(buildDic(loadConfig())))); $response = emit(buildPsr7Response(renderResponse(dispatchBusinessLogic($dispatcher, $request, new Response())))); Having to dissect expressions like the above from inside-out is not a natural way to read how the operations are actually occurring though. It's far easier to read them from left-to-right or top-to-bottom, and this is where the advantages of the pipe operator can really be seen. So I'm definitely +1 on this feature - it's something I really miss from Elixir. -Tom

Arvids Godjuks

10 years ago
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 :) On Sat, 30 Apr 2016, 09:50 Marco Pivetta, <ocramius@gmail.com> wrote:

Fleshgrinder

10 years ago
On 4/30/2016 8:49 AM, Marco Pivetta wrote:
> 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

Marco Pivetta

10 years ago
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". On Apr 30, 2016 17:05, "Fleshgrinder" <php@fleshgrinder.com> wrote:

Fleshgrinder

10 years ago
On 4/30/2016 5:23 PM, Marco Pivetta wrote:
> 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". > On Apr 30, 2016 17:05, "Fleshgrinder" <php@fleshgrinder.com> wrote: >
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

Fleshgrinder

10 years ago
On 4/30/2016 5:51 PM, Fleshgrinder wrote:
> $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

Dan Ackroyd

10 years ago
On 30 April 2016 at 07:49, Marco Pivetta <ocramius@gmail.com> wrote:
> 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

Lester Caine

10 years ago
On 01/05/16 15:39, Dan Ackroyd wrote:
> 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

Larry Garfield

10 years ago
On 04/29/2016 07:19 PM, Stanislav Malyshev wrote:
> 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

Lester Caine

10 years ago
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. :-)

Lester Caine

10 years ago
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. :-)

Sara Golemon

10 years ago
On Sat, Apr 30, 2016 at 11:41 AM, <lester@lsces.co.uk> wrote:
> 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

Michael Wallner

10 years ago
On 30/04/16 20:57, Sara Golemon wrote:
> On Sat, Apr 30, 2016 at 11:41 AM, <lester@lsces.co.uk> wrote: >> 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

Sara Golemon

10 years ago
On Mon, May 2, 2016 at 12:56 AM, Michael Wallner <mike@php.net> wrote:
> On 30/04/16 20:57, Sara Golemon wrote: >> 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

Stas Malyshev

10 years ago
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 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. :-)
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

Stas Malyshev

10 years ago
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 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. :-)
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

Stephen Coakley

10 years ago
On 04/30/2016 01:05 PM, Larry Garfield wrote:
> On 04/29/2016 07:19 PM, Stanislav Malyshev 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?") > > --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

Jesse Schalken

10 years ago
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.

Lester Caine

10 years ago
On 29/04/16 20:58, Sara Golemon wrote:
> 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

Marco Pivetta

10 years ago
Relevant: https://youtu.be/UvD1VjRvGIk On Apr 30, 2016 15:52, "Lester Caine" <lester@lsces.co.uk> wrote:

Lester Caine

10 years ago
On 30/04/16 14:57, Marco Pivetta wrote:
> 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

Peter Lind

10 years ago
On 30 Apr 2016 16:43, "Lester Caine" <lester@lsces.co.uk> wrote:
> > On 30/04/16 14:57, Marco Pivetta wrote: > > 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?

rich gray

10 years ago
On 30/04/2016 15:43, Lester Caine wrote:
> 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..

Lester Caine

10 years ago
On 30/04/16 16:03, rich gray wrote:
>> 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

Paul M Jones

10 years ago
> On Apr 29, 2016, at 14:58, Sara Golemon <pollita@php.net> wrote: > > 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

Rowan Collins

10 years ago
On 29/04/2016 20:58, Sara Golemon wrote:
> 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]

François Laupretre

10 years ago
Ho Rowan, Le 01/05/2016 01:14, Rowan Collins a écrit :
> On 29/04/2016 20:58, Sara Golemon wrote: >> 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, >
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

Stephen Coakley

10 years ago
On 04/30/2016 06:14 PM, Rowan Collins wrote:
> On 29/04/2016 20:58, Sara Golemon wrote: > 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.
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

Terry Cullen

10 years ago
On 3 May 2016 at 10:57, Stephen Coakley <me@stephencoakley.com> wrote:
> On 04/30/2016 06:14 PM, Rowan Collins wrote: > >> On 29/04/2016 20:58, Sara Golemon wrote: >> 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. >> > > 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 > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
​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

Fleshgrinder

10 years ago
On 5/3/2016 4:53 AM, Terry Cullen wrote:
> ​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

Sara Golemon

10 years ago
On Tue, May 3, 2016 at 9:30 AM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 5/3/2016 4:53 AM, Terry Cullen wrote: >> 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

Lester Caine

10 years ago
On 03/05/16 19:12, Sara Golemon wrote:
> 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

Sara Golemon

10 years ago
On Tue, May 3, 2016 at 11:29 AM, Lester Caine <lester@lsces.co.uk> wrote:
> 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

Fleshgrinder

10 years ago
On 5/3/2016 8:12 PM, Sara Golemon wrote:
> 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. On 5/3/2016 8:12 PM, Sara Golemon wrote:
> 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. On 5/3/2016 8:12 PM, Sara Golemon wrote:
> 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

Sara Golemon

10 years ago
On Tue, May 3, 2016 at 11:36 AM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 5/3/2016 8:12 PM, Sara Golemon wrote: >> 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.
> On 5/3/2016 8:12 PM, Sara Golemon wrote: >> 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. >
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

Fleshgrinder

10 years ago
On 5/3/2016 8:45 PM, Sara Golemon wrote:
>> 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

Sara Golemon

10 years ago
On Tue, May 3, 2016 at 11:52 AM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 5/3/2016 8:45 PM, Sara Golemon wrote: >> 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

Jesse Schalken

10 years ago
On Tue, May 3, 2016 at 12:53 PM, Terry Cullen <terry@terah.com.au> wrote:
> ​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.

Sara Golemon

10 years ago
On Mon, May 2, 2016 at 5:57 PM, Stephen Coakley <me@stephencoakley.com> wrote:
> 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

Lester Caine

10 years ago
On 03/05/16 05:49, Sara Golemon wrote:
> 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

Rowan Collins

10 years ago
Stephen Coakley wrote on 03/05/2016 01:57:
> On 04/30/2016 06:14 PM, Rowan Collins wrote: >> 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. > > 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]

Jesse Schalken

10 years ago
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. On Sat, Apr 30, 2016 at 5:58 AM, Sara Golemon <pollita@php.net> wrote:

Yasuo Ohgaki

10 years ago
Hi Sara, On Sat, Apr 30, 2016 at 4:58 AM, Sara Golemon <pollita@php.net> wrote:
> 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

Sara Golemon

10 years ago
On Fri, Apr 29, 2016 at 12:58 PM, Sara Golemon <pollita@php.net> wrote:
> 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

Pierre Joye

10 years ago
Hi Sara, On Apr 30, 2016 3:11 AM, "Sara Golemon" <pollita@php.net> wrote:
> > 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

Jesse Schalken

10 years ago
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; On Sat, Apr 30, 2016 at 5:58 AM, Sara Golemon <pollita@php.net> wrote:

Rowan Collins

10 years ago
On 17/05/2016 11:55, Jesse Schalken wrote:
> $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]

Jesse Schalken

10 years ago
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.

Rowan Collins

10 years ago
On 17/05/2016 13:48, Jesse Schalken wrote:
> > 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]

Sara Golemon

10 years ago
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. 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

Rowan Collins

10 years ago
On 17/05/2016 18:46, Sara Golemon wrote:
> 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]