On Fri, Feb 7, 2025, at 22:04, Larry Garfield wrote:
> Merging a few replies together here, since they overlap. Also reordering a few of Tim's
> comments...
>
> On Fri, Feb 7, 2025, at 7:32 AM, Tim Düsterhus wrote:
> > Hi
> >
> > Am 2025-02-07 05:57, schrieb Larry Garfield:
> >> It is now back with a better implementation (many thanks to Ilija for
> >> his help and guidance in that), and it's nowhere close to freeze, so
> >> here we go again:
> >>
> >> https://wiki.php.net/rfc/pipe-operator-v3
> >
> > There's some editorial issues:
> >
> > 1. Status: Draft needs to be updated.
> > 2. The RFC needs to be added to the overview page.
> > 3. List formatting issues in “Future Scope” and “Patches and Tests”.
> >
> > Would also help having a closed voting widget in the “Proposed Voting
> > Choices” section to be crystal clear on what is being voted on (see
> > below the next quote).
>
> I split pipes off from the Composition RFC late last night right before posting; I guess I
> missed a few things while doing so. :-/ Most notably, the Compose section is now removed from
> pipes, as it is not in scope for this RFC. (As noted, it's going to be more work so has its
> own RFC..) Sorry for the confusion. I think it should all be handled now.
>
> > 5. The “References” (as in reference variables) section would do well
> > with an example of what doesn't work.
>
> Example block added.
>
> > 9. In the “Why in the engine?” section: The RFC makes a claim about
> > performance.
> >
> > Do you have any numbers?
>
> Not currently. The statements here are based on simply counting the number of function calls
> necessary, and PHP function calls are sadly non-cheap. In previous benchmarks of my own libraries
> using my Crell/fp library, I did find that the number of function calls involved in some tight pipe
> operations was both a performance and debugging concern, but I don't have any hard numbers
> laying about at present to share.
>
> If you think that's critical, please advise on how to best get meaningful numbers here.
>
> Regarding the equivalency of pipes:
>
> Tim Düsterhus wrote:
> > 4. “That is, the following two code fragments are also exactly
> > equivalent:”.
> >
> > I do not believe this is true (specifically referring to the “exactly”
> > word in there), since the second code fragment does not have the short
> > closures, which likely results in an observable behavioral difference
> > when throwing Exceptions (in the stack trace) and also for debuggers.. Or
> > is the implementation able to elide the the extra closure? (Of course
> > there's also the difference between the temporary variable existing,
> > with would be observable for get_defined_vars()
and possibly
> > destructors / object lifetimes).
>
> Thomas Hruska wrote:
> > The repeated assignment to $temp in your second example is _not_
> > actually equal to the earlier example as you claim. The second example
> > with all of the $temp variables should, IMO, just be:
> >
> > $temp = "Hello World";
> > $result = array_filter(array_map('strtoupper',
> > str_split(htmlentities($temp))), fn($v) { return $v != 'O'; });
>
> Juris Evertovskis wrote:
> > 3. Does the implementation actually turn 1 |> f(...) |> g(...)
into
> > $π = f(1); g($π)
? Is g(f(1))
not performanter? Or is the
> > engine
> > clever enough with the var reuse anyways?
>
> There's some subtlety here on these points. The v2 RFC used the lexer to mutate $a |>
> $b |> $c into the same AST as $c($b($a)), which would then compile as though that had been
> written in the first place. However, that made addressing references much harder, and there's
> an important caveat around order of operations. (See below.) The v3 RFC instead uses a compile
> function to take the AST of $a |> $b |> $c and produce opcodes that are effectively equivalent
> to $t = $b($a); $t = $c($t); I have not compared to see if they are the precise same opcodes, but
> they net effect is the same. So "effectively equivalent" may be a more accurate
> statement.
>
> In particular, Tim is correct that, technically, the short lambdas would be used as-is, so
> you'd end up with the equivalent of:
>
> $temp = (fn($x) => array_map(strtoupper(...), $x))($temp);
>
> I'm not sure if there's a good way to automatically unwrap the closure there. (If
> someone knows of one, please share; I'm fine with including it.) However, the intent is that
> it would be largely unnecessary in the future with a revised PFA implementation, which would obviate
> the need for the explicit wrapping closure. You would instead write
>
> $a |> array_map(strtoupper(...), ?);
>
> Alternatively, one can use higher order user-space functions already. In trivial cases:
>
> function amap(Closure $fn): Closure {
> return fn(array $x) => array_map($fn, $x);
> }
>
> $a |> amap(strtoupper(...));
>
> Which I am already using in Crell/fp and several libraries that leverage it, and it's
> quite ergonomic.
>
> There's a whole bunch of such simple higher order functions here:
> https://github.com/Crell/fp/blob/master/src/array.php
> https://github.com/Crell/fp/blob/master/src/string.php
>
> Which leads to the subtle difference between that and the v2 implementation, and why
> Thomas' statement is incorrect. If the expression on the right side that produces a Closure
> has side effects (output, DB interaction, etc.), then the order in which those side effects happen
> may change with the different restructuring. With all pure functions, that won't make a
> practical difference, and normally one should be using pure functions, but that's not something
> PHP can enforce.
>
> I don't think there would be an appreciable performance difference between the two
> compiled versions, either way, but using the temp-var approach makes dealing with references easier,
> so it's what we're doing.
>
> Juris Evertovskis wrote:
> > 1. Do you think it would be hard to add some shorthand for `|>
> > $condition ? $callable : fn($😐) => $😐`?
>
> I'm not sure I follow here. Assuming you're talking about "branch in the next
> step", the standard way of doing that is with a higher order user-space function. Something
> like:
>
> function cond(bool $cond, Closure $t, Closure $f): Closure {
> return $cond ? $t : $f;
> }
>
> $a |> cond($config > 10, bigval(...), smallval(...)) |> otherstuff(...);
>
> I think it's premature to try and bake that logic into the language, especially when I
> don't know of any other function-composition-having language that does so at the language level
> rather than the standard library level. (There are a number of fun operations people build into
> pipelines, but they are all generally done in user space.)
>
> --Larry Garfield
>
Put another way, what is the order of operations for this new operator?
For example, what is the output of
$x ? $y |> strlen(…) : $z
$x + $y |> sqrt(…) . EOL
Etc.
I noticed this seems to be missing from the RFC. As a new operator, I think it should be important
to specify that.
— Rob