On Fri, May 21, 2021, at 2:52 AM, Nicolas Grekas wrote:
> Sorry for self-reply, this needs some clarifications :)
> 
> Le ven. 21 mai 2021 à 09:17, Nicolas Grekas <nicolas.grekas+...@gmail.com>
> a écrit :


> >> There's been a lot of rapid iteration, experimentation, and rejection.
> >> The most recent alternatives are this one from Levi:
> >>
> >> https://gist.github.com/morrisonlevi/f7cf949c02f5b9653048e9c52dd3cbfd
> >>
> >> And this one from me:
> >>
> >> https://gist.github.com/Crell/ead27e7319e41ba98b166aba89fcd7e8
> >>
> >> The main takeaways (to give context to Nikita's proposal):
> >>
> >> * Because of optional arguments, using the same symbol for "copy one
> >> parameter from the underlying function" and "copy all remaining parameters
> >> from the underlying function" is not viable.  It runs into oddball cases
> >> where you may intend to only use one argument but end up using multiple,
> >> especially in array operation functions that sometimes silently pass keys
> >> along to callbacks if they can.  Hence the separate ? and ... that were
> >> proposed.
> >>
> >
> > I've read that some ppl think this should be fought, but that's actually a
> > critical feature of the engine when writing BC layers by adding extra
> > arguments via this possibility.
> >
> 
> I'm talking about implicit extra arguments accessed via func_get_args()
> here.

The issue is with optional arguments that would get auto-forwarded, even when 
you don't want to use them.  (It wasn't obvious to me for a long time that this 
issue existed either.)  The classic example, ported from Javascript:

function printInt(string $value, int $base = 10): void
{
    echo intval($value, $base), "\n";
}

$array = ['10', '10', '10', '10', '10'];

array_walk($array, 'printInt');

Results in:

10
0
2
3
4

The function has multiple arguments, but we only want to use one, but functions 
like array_walk() will try to use  the optional one if they can... even if 
doing so makes no logical sense.  Having ? capture all arguments 
unconditionally would also mean it captures optional arguments even if you 
don't want it to.  That's why we need to have the separate ... symbol for when 
you really do mean "and all the rest".

In this case, we need printInt(?) to only pass $value through.  If you really 
did want to use the array key for the base for some reason, you could do 
printInt(?, ?) or printInt(...).

I think the plan would be that extra passed arguments to a partialed function 
would still forward through, but that may end up being more difficult than 
expected.  

> In the 2nd gist, I read "If the current position is a ..., copy all
> > remaining parameters from the underlying function, including a variadic."
> > But to me it's important that all extra arguments are forwarded to the
> > partialized callable, where ... is used or not.
> > Please clarify this point if possible.

I think this is the same as the previous question?

(Note that several vocal people have argued that the way PHP passes along 
extraneous arguments is a bug, not a feature.  I don't have a strong feeling 
about it, but some do.)

> > * Named arguments make things more complicated.  One of the questions is
> >> whether named placeholders should be supported or not.  And if they are,
> >> does that mean you can effectively reorder the arguments in the partial
> >> application, and what does that mean for usability.  It gets complicated
> >> and scope-creepy fast.
> >>
> >
> > For the userland pov, the principle of least surprise would say we should
> > answer yes to both questions.
> 
> Reading your gists, I'm with drawing this expectation.
> 
> Since calls reorder named arguments automatically, we could expect the same
> for PFAs.
> That is:
> since foo(A: 1, B: 2) is exactly the same as foo(B: 2, A: 1)
> we could also expect that foo(A: ?, B: ?) would be exactly the same
> foo(B:?, A:?)
> 
> The issue here is that the syntax can be interpreted as both a new
> signature and as a partial call.
> If we stay pure to the intend of PFAs, argument reordering shouldn't be
> allowed IMHO. Aka this should be interpreted only as a partial call.
> 
> *If* we need a way to reorder arguments, I think it would be natural also
> to be able to *rename* arguments.

Which is also where the rabbit hole gets extra deep, and why I am perfectly 
happy to just disallow named placeholders.  In the rare use cases where you 
really do want/need to reorder arguments and do other esoteric things, short 
lambdas already let you do that much more explicitly.  That's an edge case that 
already has a solution.  We're focused here on making the most common cases 
nicer.

> That's where my syntax proposal based on short closures might be handy:
> 
> fn($a, $b, ...) => $callable($b, a: $a, ...)

At that point we're not getting any benefit over just using arrow functions.  
The point of PFA is that it's partially calling a function as-is, in a compact 
way.  That in PHP we'd be implementing it as yet-another-closure object is an 
implementation detail.

> >> We also went down a rabbit hole of trying to make argument reordering
> >> work because some people asked for it, but as noted that's a very deep
> >> rabbit hole.
> >>
> >
> > Then maybe named args should be forbidden for "?" placeholders. This would
> > keep this possibility open of the future and close the "principle of least
> > surprise" expectation if there are. Aka foo(bar: ?) should be a parse error
> > for now at least.

That is precisely the conclusion I reached and what my writeup says: The only 
placeholders are positional placeholders.  Named arguments are fine, but not 
named placeholders.

> > My own take is that the PFA discussion has been overly-bikeshedded, which
> >> is unfortunate since I think we're quite close to now having a workable
> >> answer that covers most reasonable use cases.  While I agree Nikita's RFC
> >> here would be an improvement over 8.0, I don't think throwing in the towel
> >> on PFA yet is a good idea.  It's a much more robust and powerful approach
> >> that still gets us the "first class callable" syntax we all want (at least
> >> I assume we all do), and lots of additional power to boot.  I'd rather see
> >> us try to drive PFA home to completion.  If that proves impossible by early
> >> July, then this RFC would still get us something this cycle, as long as the
> >> syntax is still compatible with PFA.  (Otherwise whenever PFA gets sorted
> >> out in the future we end up with yet-more-ways to do the same thing that
> >> are not optimizations of each other but just competing syntax, in which
> >> case no one wins.)
> >>
> >
> > I agree. Let's figure out PFAs!
> >
> > If I may add to the bikeshedding, and since PFAs are like others said
> > "short lambas on steroids", what about borrowing the syntax from them?
> >
> > Here would be the first-class callable syntax:
> >
> > fn(...) => $callable(...)
> >
> > and for PFAs:
> >
> > fn(...) => $callable(?, 42, ...)
> >
> > Nicolas

As above, not really viable.


Also, to answer Andreas as long as I'm here: Supporting "new Foo" with PFA is a 
separate question.  As noted in the currently written draft of the RFC, the 
problem is that object creation is not a function call; the object is created, 
then the __construct initializer is called.  A naive PFA implementation would 
therefore create the object once, couple it to the partial, and then calling 
the partial repeatedly would return the same object, rather than repeatedly 
making a new object.  Joe already figured out how to special case that, though, 
so it's a non-issue.

Both Levi's writeup and mine could support PFA constructors, AFAIK.  

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to