Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95% complete. (There's some edge cases he's still sorting out, but all of the typical cases should work already.) Most of the design work comes from Levi Morrison and Paul Crovella. I helped out with the tests, a few edge bits, and general instigator/nudge. :-)
Discuss.
--
Larry Garfield
larry@garfieldtech.com
2021-04-25 21:25 GMT+02:00, Larry Garfield larry@garfieldtech.com:
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of the
typical cases should work already.) Most of the design work comes from Levi
Morrison and Paul Crovella. I helped out with the tests, a few edge bits,
and general instigator/nudge. :-)Discuss.
--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Nice. :) Are there any other motivating use-cases besides pipe
operator (that are relevant for web dev)?
Olle
On Sun, Apr 25, 2021 at 10:26 PM Larry Garfield larry@garfieldtech.com
wrote:
Amazing, I've wanted this for so long! Have you considered extending this
syntax to OOP, e.g. $obj->method(?) or SomeClass::staticMethod(?)?
On Sun, Apr 25, 2021 at 10:51 PM Olle Härstedt olleharstedt@gmail.com
wrote:
Nice. :) Are there any other motivating use-cases besides pipe
operator (that are relevant for web dev)?
One reason I'm personally excited about this proposal is that it would
allow static analysis in more cases, e.g.
$param = 'fooBar'; // What's this string? A user name? Elon Musk's favorite
color? Text of US Constitution?
somefunc($param);
function somefunc($cb) {
$cb(123, 'meh'); // What parameter types does this accept?
}
Allowing syntax like $param = fooBar(?, ?); would indicate clearly what
this variable receives. Analysis tools would then be able to check if such
a function exists and whether it would receive parameters of expected types.
--
Best regards,
Max Semenik
2021-04-25 21:07 GMT, Max Semenik maxsem.wiki@gmail.com:
On Sun, Apr 25, 2021 at 10:26 PM Larry Garfield larry@garfieldtech.com
wrote:Amazing, I've wanted this for so long! Have you considered extending this
syntax to OOP, e.g. $obj->method(?) or SomeClass::staticMethod(?)?On Sun, Apr 25, 2021 at 10:51 PM Olle Härstedt olleharstedt@gmail.com
wrote:Nice. :) Are there any other motivating use-cases besides pipe
operator (that are relevant for web dev)?One reason I'm personally excited about this proposal is that it would
allow static analysis in more cases, e.g.$param = 'fooBar'; // What's this string? A user name? Elon Musk's favorite
color? Text of US Constitution?somefunc($param);
function somefunc($cb) {
$cb(123, 'meh'); // What parameter types does this accept?
}Allowing syntax like $param = fooBar(?, ?); would indicate clearly what
this variable receives. Analysis tools would then be able to check if such
a function exists and whether it would receive parameters of expected
types.
Like an alternative to value objects (class Email ...
)? Not sure I
understand how, sorry.
Olle
2021-04-25 21:07 GMT, Max Semenik maxsem.wiki@gmail.com:
On Sun, Apr 25, 2021 at 10:26 PM Larry Garfield larry@garfieldtech.com
wrote:Amazing, I've wanted this for so long! Have you considered extending this
syntax to OOP, e.g. $obj->method(?) or SomeClass::staticMethod(?)?On Sun, Apr 25, 2021 at 10:51 PM Olle Härstedt olleharstedt@gmail.com
wrote:Nice. :) Are there any other motivating use-cases besides pipe
operator (that are relevant for web dev)?One reason I'm personally excited about this proposal is that it would
allow static analysis in more cases, e.g.$param = 'fooBar'; // What's this string? A user name? Elon Musk's favorite
color? Text of US Constitution?somefunc($param);
function somefunc($cb) {
$cb(123, 'meh'); // What parameter types does this accept?
}Allowing syntax like $param = fooBar(?, ?); would indicate clearly what
this variable receives. Analysis tools would then be able to check if such
a function exists and whether it would receive parameters of expected
types.--
Best regards,
Max Semenik
By the way, for pipe operators to be really useful, it should probably
be possible to combine with middleware.
Olle
By the way, for pipe operators to be really useful, it should probably
be possible to combine with middleware.Olle
Pipes are their own RFC I plan to bring back up after this one gets closer to a vote or passes, as they complement each other nicely. I'm not sure what you mean here, though. Pipes are a middleware style, just a different approach than the deeply nested call stack that is typical today.
--Larry Garfield
2021-04-26 0:19 GMT+02:00, Larry Garfield larry@garfieldtech.com:
By the way, for pipe operators to be really useful, it should probably
be possible to combine with middleware.Olle
Pipes are their own RFC I plan to bring back up after this one gets closer
to a vote or passes, as they complement each other nicely. I'm not sure
what you mean here, though. Pipes are a middleware style, just a
different approach than the deeply nested call stack that is typical today.--Larry Garfield
I was thinking of middleware as defined in PSR (since that's what
middleware frameworks are built on). Would be nice with a connection
there.
Olle
On Sun, Apr 25, 2021 at 10:26 PM Larry Garfield larry@garfieldtech.com
wrote:Amazing, I've wanted this for so long! Have you considered extending this
syntax to OOP, e.g. $obj->method(?) or SomeClass::staticMethod(?)?
It works for methods as well, as stated in the proposal section. I
think there aren't any examples in the RFC just because functions make
for shorter examples ^_^ but we should add some to help make that
clear.
On Sun, Apr 25, 2021 at 10:26 PM Larry Garfield larry@garfieldtech.com
wrote:Amazing, I've wanted this for so long! Have you considered extending this
syntax to OOP, e.g. $obj->method(?) or SomeClass::staticMethod(?)?It works for methods as well, as stated in the proposal section. I
think there aren't any examples in the RFC just because functions make
for shorter examples ^_^ but we should add some to help make that
clear.
Ha, yes, I didn't realize we were missing those. :-) I just added a Methods section. In short, yes, it works for methods, functions, closures, basically anything you can otherwise call.
--Larry Garfield
2021-04-25 21:25 GMT+02:00, Larry Garfield larry@garfieldtech.com:
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of the
typical cases should work already.) Most of the design work comes from Levi
Morrison and Paul Crovella. I helped out with the tests, a few edge bits,
and general instigator/nudge. :-)Discuss.
--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Nice. :) Are there any other motivating use-cases besides pipe
operator (that are relevant for web dev)?Olle
--
To unsubscribe, visit: https://www.php.net/unsub.php
Yes, of course! PHP has many uses of array_map
, array_filter
, etc,
that take a callback as a parameter. It's hard to create good examples
on the spot, but here's an example, at least:
array_filter($array, in_array(?, $haystack, strict: true))
On Sun, Apr 25, 2021 at 9:26 PM Larry Garfield larry@garfieldtech.com
wrote:
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of the
typical cases should work already.) Most of the design work comes from
Levi Morrison and Paul Crovella. I helped out with the tests, a few edge
bits, and general instigator/nudge. :-)Discuss.
--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Heya, this is an interesting feature!
The way I understand it, it adds the ability to turn a call to an object
method into a callable?
class CategorizedThings {
private array $theThings = [];
public function addOneThing(mixed $thing, string $category): void {
$this->theThings[$category] = $thing;
}
}
$things = new CategorizedThings();
$adder = $things->addOneThing(?, 'a category');
foreach ($repository->findAll() as $aThing) {
$adder($aThing);
}
// which would be the same as:
$things = new CategorizedThings();
$adder = function (mixed $aThing) use ($things) {
$things->addOneThing($aThing, 'a category');
}
foreach ($repository->findAll() as $aThing) {
$adder($aThing);
}
I assume the following would also be possible?
class Something {
public static function toString(mixed $v): string {
return (string) $v;
}
}
function toString(mixed $v) : string {
return (string) $v;
}
array_map(Something::toString(?), [1, 2, 3]);
array_map(toString(?), [1, 2, 3]);
// instead of
array_map([Something::class, 'toString'], [1, 2, 3])
array_map('toString', [1, 2, 3]);
If this is indeed the scenario, would it be worth adding a syntax to
indicate all parameters can be placeholders? When a signature has
multiple arguments, toString(?, ?, ?, ?)
could become toString(...?)
or
maybe something like toString(*)
?
If this is indeed the scenario, would it be worth adding a syntax to
indicate all parameters can be placeholders? When a signature has
multiple arguments,toString(?, ?, ?, ?)
could becometoString(...?)
or
maybe something liketoString(*)
?
At the moment, there isn't any difference in foo(?)
and foo(?, ?, ?, ?, ?, ?, ?, ?)
. If there is one placeholder, then it will bind (or
fix) the non-placeholder arguments and then create a closure-like with
a signature of the remaining parameters. Adding more placeholders does
not change anything, except to adjust positions:
foo(?, $bar, ?, $baz)
In this case the two placeholders are meaningful because they adjust
which positional arguments are bound. This is the only difference in
behavior in one placeholder or more.
In an earlier version of the RFC I had ...
so you could write
foo(...)
. Some of the RFC authors didn't like this, because it was
functionally no different and brought up "when to use ? and when to
use ..." as a question.
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of
the typical cases should work already.) Most of the design work comes
from Levi Morrison and Paul Crovella. I helped out with the tests, a
few edge bits, and general instigator/nudge. :-)Discuss.
It looks like the conversation has died down, and it's been two weeks, so pending any other notable feedback I'll open a vote on this RFC on Thursday or Friday.
--Larry Garfield
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of
the typical cases should work already.) Most of the design work comes
from Levi Morrison and Paul Crovella. I helped out with the tests, a
few edge bits, and general instigator/nudge. :-)Discuss.
It looks like the conversation has died down, and it's been two weeks, so
pending any other notable feedback I'll open a vote on this RFC on Thursday
or Friday.--Larry Garfield
LGTM, thanks for the RFC!
What about visibility? I suppose this works even outside the object scope?
should this be mentionned?
$foo = $this->somePrivateMethod(1, ?)
Would it make sense to support a way to use a placeholder for "all
remaining args"?
Eg:
$foo = some_func(1, ...?)
Combined with my previous comment, this could replace
Closure::fromCallable() by a language construct, which is something that we
already discussed in another thread (we talked about some ::function
special suffix instead):
$foo = $this->somePrivateMethod(...?)
In this last form, we might make this result in Closure::fromCallable()
exactly. Aka no increase of the depth of the stack trace.
BTW, ideally, partial functions should not increase the depth of the
stacktrace at all. Do they?
Nicolas
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of
the typical cases should work already.) Most of the design work comes
from Levi Morrison and Paul Crovella. I helped out with the tests, a
few edge bits, and general instigator/nudge. :-)Discuss.
It looks like the conversation has died down, and it's been two weeks, so
pending any other notable feedback I'll open a vote on this RFC on Thursday
or Friday.--Larry Garfield
LGTM, thanks for the RFC!
What about visibility? I suppose this works even outside the object scope?
should this be mentionned?
$foo = $this->somePrivateMethod(1, ?)
We're pretty sure it will work, and if you return $foo to another caller and then call it, it will work fine. We'll add a test to confirm that.
Would it make sense to support a way to use a placeholder for "all
remaining args"?
Eg:
$foo = some_func(1, ...?)
That's already implicit in the way it works now. If you placeholder at least one parameter, and then don't fill in all of the remaining parameters, any additional trailing parameters are always placeholdered. So $obj->foo(?) will always return a partial that has the same arity as foo() does, regardless of how many parameters it has.
Combined with my previous comment, this could replace
Closure::fromCallable() by a language construct, which is something that we
already discussed in another thread (we talked about some ::function
special suffix instead):
$foo = $this->somePrivateMethod(...?)
Yep. That's noted in the RFC. Although not the primary intent, a nice side effect is that any_func(?) is now a safe way to turn any function/method into a callable to reference it. That renders ::function or similar 98% unnecessary. (The remaining cases are mostly where you need to collect data statically for compilation or similar.)
In this last form, we might make this result in Closure::fromCallable()
exactly. Aka no increase of the depth of the stack trace.BTW, ideally, partial functions should not increase the depth of the
stacktrace at all. Do they?Nicolas
They currently do, since they work by creating a Closure-esque object called Partial with an __invoke() method. However, if you partial the same thing multiple times then only one stack level gets added. That is:
function test($a, $b, $c, $d) { throw new Exception(); }
$first = test(?, ?, ?, ?);
$second = $first(1, ?);
$third = $second(2, ?);
$fourth = $third(3, ?);
$result = $fourth(4);
The stack at the end will contain only one partial "level", not 4.
--Larry Garfield
Greetings, Internalians!
I would like to offer for your consideration another RFC,
specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about
95%
complete. (There's some edge cases he's still sorting out, but all
of
the typical cases should work already.) Most of the design work
comes
from Levi Morrison and Paul Crovella. I helped out with the tests, a
few edge bits, and general instigator/nudge. :-)Discuss.
It looks like the conversation has died down, and it's been two weeks,
so
pending any other notable feedback I'll open a vote on this RFC on
Thursday
or Friday.--Larry Garfield
LGTM, thanks for the RFC!
What about visibility? I suppose this works even outside the object
scope?
should this be mentionned?
$foo = $this->somePrivateMethod(1, ?)We're pretty sure it will work, and if you return $foo to another caller
and then call it, it will work fine. We'll add a test to confirm that.Would it make sense to support a way to use a placeholder for "all
remaining args"?
Eg:
$foo = some_func(1, ...?)That's already implicit in the way it works now. If you placeholder at
least one parameter, and then don't fill in all of the remaining
parameters, any additional trailing parameters are always placeholdered.
So $obj->foo(?) will always return a partial that has the same arity as
foo() does, regardless of how many parameters it has.Combined with my previous comment, this could replace
Closure::fromCallable() by a language construct, which is something that
we
already discussed in another thread (we talked about some ::function
special suffix instead):
$foo = $this->somePrivateMethod(...?)Yep. That's noted in the RFC. Although not the primary intent, a nice
side effect is that any_func(?) is now a safe way to turn any
function/method into a callable to reference it. That renders ::function
or similar 98% unnecessary. (The remaining cases are mostly where you need
to collect data statically for compilation or similar.)
Great news!
In this last form, we might make this result in Closure::fromCallable()
exactly. Aka no increase of the depth of the stack trace.
BTW, ideally, partial functions should not increase the depth of the
stacktrace at all. Do they?Nicolas
They currently do, since they work by creating a Closure-esque object
called Partial with an __invoke() method. However, if you partial the same
thing multiple times then only one stack level gets added.
Nice. Would it be possible to optimize this and remove the extra frame? At
least maybe for the (?) case?
This makes me wonder: can we create a partial programmatically? Wouldn't
that be needed for some use cases?
Partial::createFromCallable($callable, the-args)?
Nicolas
BTW, ideally, partial functions should not increase the depth of the
stacktrace at all. Do they?Nicolas
They currently do, since they work by creating a Closure-esque object
called Partial with an __invoke() method. However, if you partial the same
thing multiple times then only one stack level gets added.Nice. Would it be possible to optimize this and remove the extra frame? At
least maybe for the (?) case?
I'd have to defer to Joe (he wrote the implementation), but I suspect not. The partial has to be there to carry around the information of what callable to actually call and what the bound parameters are. At that point, it's likely more work to decompose the Partial internally before calling it than to just call the Partial itself.
This makes me wonder: can we create a partial programmatically? Wouldn't
that be needed for some use cases?
Partial::createFromCallable($callable, the-args)?Nicolas
I cannot think of a use case where that would be needed. Since you can partial-ize any callable, including a dynamic one, if you needed to do something like partial-ize one of a series of function calls you can do that already:
$c = match($some_input) {
'A' => 'func_a',
'B' => 'func_b',
'C' => 'func_c',
};
$p = $c(1, 2 ?, 4);
Though at that point, just partialing them in the first place inside the match would be better as then you never have a function name in a string to begin with.
--Larry Garfield
BTW, ideally, partial functions should not increase the depth of the
stacktrace at all. Do they?Nicolas
They currently do, since they work by creating a Closure-esque object
called Partial with an __invoke() method. However, if you partial the
same
thing multiple times then only one stack level gets added.Nice. Would it be possible to optimize this and remove the extra frame?
At
least maybe for the (?) case?I'd have to defer to Joe (he wrote the implementation), but I suspect
not. The partial has to be there to carry around the information of what
callable to actually call and what the bound parameters are. At that
point, it's likely more work to decompose the Partial internally before
calling it than to just call the Partial itself.
Then I suppose that removing the extra frame would mean embedding that
state into Closure objects. Would it make sense to extend Closure this way?
Isn't the new Partial class an implementation artefact that should be
removed? If not, what are the userland-side reasons to keep it?
If the Partial class has to be kept, can it be optimized out back to a
closure when no arguments are bound? (aka the "$closure = $this->method(?)"
case)
This makes me wonder: can we create a partial programmatically? Wouldn't
that be needed for some use cases?
Partial::createFromCallable($callable, the-args)?Nicolas
I cannot think of a use case where that would be needed. Since you can
partial-ize any callable, including a dynamic one, if you needed to do
something like partial-ize one of a series of function calls you can do
that already:$c = match($some_input) {
'A' => 'func_a',
'B' => 'func_b',
'C' => 'func_c',
};$p = $c(1, 2 ?, 4);
Though at that point, just partialing them in the first place inside the
match would be better as then you never have a function name in a string to
begin with.
Here is a use case: high-order argument resolvers / function reducers.
What I mean is a function that takes a callable as arguments, resolves as
many args of the callable as it can using whatever logic fits, and returns
a callable with fewer arguments (only the non-resolved ones - aka a
Partial).
Nicolas
This makes me wonder: can we create a partial programmatically? Wouldn't
that be needed for some use cases?
Partial::createFromCallable($callable, the-args)?Nicolas
I cannot think of a use case where that would be needed. Since you can
partial-ize any callable, including a dynamic one, if you needed to do
something like partial-ize one of a series of function calls you can do
that already:$c = match($some_input) {
'A' => 'func_a',
'B' => 'func_b',
'C' => 'func_c',
};$p = $c(1, 2 ?, 4);
Though at that point, just partialing them in the first place inside the
match would be better as then you never have a function name in a string to
begin with.Here is a use case: high-order argument resolvers / function reducers.
What I mean is a function that takes a callable as arguments, resolves as
many args of the callable as it can using whatever logic fits, and returns
a callable with fewer arguments (only the non-resolved ones - aka a
Partial).
You're thinking something like an auto-wiring routine for callables?
function volume(int $x, int $y, int $z) { ... }
class Resolver {
private array $context = ['x' => 5];
public resolve(callable $c) {
foreach (get_argument_names_from_param($c) as $k) {
if (isset($this->context[$k]) {
$args[$k] = $this->context[$k];
}
}
return $c(...$args, ?);
}
}
$r = new Resolver();
$c2 = $r->resolve(volume(?));
print $c2(y: 3, z: 9);
I haven't tried running that (I don't feel like messing with the necessary reflection), but... I think it would work already? Whether that's useful in practice or not I don't know. :-)
--Larry Garfield
This makes me wonder: can we create a partial programmatically?
Wouldn't
that be needed for some use cases?
Partial::createFromCallable($callable, the-args)?Nicolas
I cannot think of a use case where that would be needed. Since you can
partial-ize any callable, including a dynamic one, if you needed to do
something like partial-ize one of a series of function calls you can do
that already:$c = match($some_input) {
'A' => 'func_a',
'B' => 'func_b',
'C' => 'func_c',
};$p = $c(1, 2 ?, 4);
Though at that point, just partialing them in the first place inside
the
match would be better as then you never have a function name in a
string to
begin with.Here is a use case: high-order argument resolvers / function reducers.
What I mean is a function that takes a callable as arguments, resolves as
many args of the callable as it can using whatever logic fits, and
returns
a callable with fewer arguments (only the non-resolved ones - aka a
Partial).You're thinking something like an auto-wiring routine for callables?
function volume(int $x, int $y, int $z) { ... }
class Resolver {
private array $context = ['x' => 5];public resolve(callable $c) {
foreach (get_argument_names_from_param($c) as $k) {
if (isset($this->context[$k]) {
$args[$k] = $this->context[$k];
}
}
return $c(...$args, ?);
}
}$r = new Resolver();
$c2 = $r->resolve(volume(?));print $c2(y: 3, z: 9);
I haven't tried running that (I don't feel like messing with the necessary
reflection), but... I think it would work already? Whether that's useful
in practice or not I don't know. :-)
Oh, indeed, that's super great!
Then remains my question about replacing Partial by Closure. I think this
would be an important feature and that would remove the need for
ReflectionPartial.
About reflection, the following behaviors are quite problematic to me:
ReflectionFunction(Closure::fromCallable($arbitrary_callable)) to
normalize all callables to a single reflection interface will no longer work
I don't understand why. Unless there is a rationale to that (which one?)
this looks arbitrary.
I very often see code like:
if (!$callable instanceof Closure) $callable =
Closure::fromCallable($callable);
$r = ReflectionFunction($callable)
The limitation of described in the RFC will make the equivalent boilerplate
much more complex in order to support 8.1
I would like this to be improved or at least better explained.
Cheers,
Nicolas
Hi Larry,
On Tue, May 11, 2021 at 8:55 PM Larry Garfield larry@garfieldtech.com
wrote:
They currently do, since they work by creating a Closure-esque object
called Partial with an __invoke() method.--Larry Garfield
-
Would it be possible to mention the
Partial
class in the RFC? From
what I understand this is what it will be returned by the
get_class($partial)
.
I guess the class will be final (similar with Closure) and will only have a
private constructor and a public __invoke method. -
As now ReflectionFunction accepts Closure|string, wouldn't it be simpler
for the users to just have it accept Closure|Partial|string to avoid
introducing another reflection class and also avoid the userland issue you
mentioned?
Alex
On Tue, May 11, 2021 at 4:39 PM Larry Garfield larry@garfieldtech.com
wrote:
It looks like the conversation has died down, and it's been two weeks, so
pending any other notable feedback I'll open a vote on this RFC on Thursday
or Friday.--Larry Garfield
My only query / point of consideration is a very minor one for what is
otherwise an enthusiastic (non-voting) +1. In relation to one of the
examples:
function whole($one, $two) { /* ... */ }
// equivalent to calling whole(1, 2, 3)
$result = whole(?, 2)(1, 3);
Is there a risk this has the potential to be confusing? We've always had
this quirk in PHP of being able to pass extra unspecified parameters in
function calls, but with the exception of variadic functions the
expectation is that they are ignored. Arguably if I saw this:
function whole($one, $two) { /* ... */ }
$partial = whole(?, 2);
$result = partial(1, 3);
I might expect it to semantically translate to:
function partial($one) { return whole($one, 2); }
with the extra parameter, 3, ignored as per convention for any other
function receiving undefined extra params. But the RFC says this parameter
would be passed to whole(). Which is not unreasonable, but yeah I guess it
strikes me it's kind of a quirk in itself to do it that way.
-Dave
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of
the typical cases should work already.) Most of the design work comes
from Levi Morrison and Paul Crovella. I helped out with the tests, a
few edge bits, and general instigator/nudge. :-)Discuss.
It looks like the conversation has died down, and it's been two weeks,
so pending any other notable feedback I'll open a vote on this RFC on
Thursday or Friday.
Naturally there was more conversation. :-) But good conversation, so the RFC has been updated again. I recommend everyone look it over another time.
Highlights:
-
Joe was able to figure out how to fold the extra partial logic into the Closure object. So the Partial and ReflectionPartial classes are gone; partial application gives you a closure object that looks like any other, but still manages to optimize away repeated partial application.
-
Some descriptions were improved.
-
Constructors already worked, with some caveats. Joe managed to remove those caveats, so they now work as you would expect them to. (See the RFC section for details.)
-
I included some references to other languages and how they handle partial application. Short version: This is the most robust and most fully featured version of any language I could find. Rock on, PHP! :-)
-
Nicolas, I went ahead and made a test for partial-application-DI-autowiring, just to see what it would look like. It looks like this:
Whether that's actually useful or not I don't know, but there it is. :-)
Since there were some substantive changes, we're pushing the vote start off until the first half of next week, Monday/Tuesday timeframe.
Thanks for your feedback, everyone!
--Larry Garfield
Le jeu. 13 mai 2021 à 19:00, Larry Garfield larry@garfieldtech.com a
écrit :
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically
syntax for partial function application.https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95%
complete. (There's some edge cases he's still sorting out, but all of
the typical cases should work already.) Most of the design work comes
from Levi Morrison and Paul Crovella. I helped out with the tests, a
few edge bits, and general instigator/nudge. :-)Discuss.
It looks like the conversation has died down, and it's been two weeks,
so pending any other notable feedback I'll open a vote on this RFC on
Thursday or Friday.Naturally there was more conversation. :-) But good conversation, so the
RFC has been updated again. I recommend everyone look it over another time.Highlights:
Joe was able to figure out how to fold the extra partial logic into the
Closure object. So the Partial and ReflectionPartial classes are gone;
partial application gives you a closure object that looks like any other,
but still manages to optimize away repeated partial application.Some descriptions were improved.
Constructors already worked, with some caveats. Joe managed to remove
those caveats, so they now work as you would expect them to. (See the RFC
section for details.)I included some references to other languages and how they handle
partial application. Short version: This is the most robust and most fully
featured version of any language I could find. Rock on, PHP! :-)Nicolas, I went ahead and made a test for
partial-application-DI-autowiring, just to see what it would look like. It
looks like this:Whether that's actually useful or not I don't know, but there it is. :-)
Since there were some substantive changes, we're pushing the vote start
off until the first half of next week, Monday/Tuesday timeframe.
That's pretty damned cool, frankly.
Thanks to everybody involved, I can't wait!
Nicolas
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
Thank you again for one more wonderful language change! I am sure this
is a huge step into the correct direction for the language. One of the
nice side effects of this proposal is that it is great for reducing
visual clutter when being used in functions like array_map()
,
array_filter()
etc. However, I have a small nitpick, which may
probably be a bit hard to implement: what about partially applied
constructors? They are sort of functions as well. For instance:
$strings = [ 'value-1', 'value-2' ];
$objects = array_map(
fn (string $s) => new ValueObject($s),
$strings
to be turned into
$strings = [ 'value-1', 'value-2' ];
$objects = array_map(new ValueObject(?), $strings);
There is unfortunately no mention of partially applied constructors in
the RFC. If it is not implemented as above, could you please consider
adding it to the Future Scope probably?
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
Thank you again for one more wonderful language change! I am sure this
is a huge step into the correct direction for the language. One of the
nice side effects of this proposal is that it is great for reducing
visual clutter when being used in functions likearray_map()
,
array_filter()
etc. However, I have a small nitpick, which may
probably be a bit hard to implement: what about partially applied
constructors? They are sort of functions as well. For instance:$strings = [ 'value-1', 'value-2' ]; $objects = array_map( fn (string $s) => new ValueObject($s), $strings
to be turned into
$strings = [ 'value-1', 'value-2' ]; $objects = array_map(new ValueObject(?), $strings);
There is unfortunately no mention of partially applied constructors in
the RFC. If it is not implemented as above, could you please consider
adding it to the Future Scope probably?--
To unsubscribe, visit: https://www.php.net/unsub.php
The GitHub PR supports this, and there is even a comment there saying
we should show this in the RFC. We'll get that updated.
On Thu, May 13, 2021 at 7:44 AM Levi Morrison
levi.morrison@datadoghq.com wrote:
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
Thank you again for one more wonderful language change! I am sure this
is a huge step into the correct direction for the language. One of the
nice side effects of this proposal is that it is great for reducing
visual clutter when being used in functions likearray_map()
,
array_filter()
etc. However, I have a small nitpick, which may
probably be a bit hard to implement: what about partially applied
constructors? They are sort of functions as well. For instance:$strings = [ 'value-1', 'value-2' ]; $objects = array_map( fn (string $s) => new ValueObject($s), $strings
to be turned into
$strings = [ 'value-1', 'value-2' ]; $objects = array_map(new ValueObject(?), $strings);
There is unfortunately no mention of partially applied constructors in
the RFC. If it is not implemented as above, could you please consider
adding it to the Future Scope probably?--
To unsubscribe, visit: https://www.php.net/unsub.php
The GitHub PR supports this, and there is even a comment there saying
we should show this in the RFC. We'll get that updated.
I was a bit too hasty. In further review it does do something, but not
what you'd want in situations like this. We are discussing among
authors whether it is technically feasible to support the expected
behavior at this time.
The GitHub PR supports this, and there is even a comment there saying
we should show this in the RFC. We'll get that updated.
Thank you for the quick reaction and for your work!
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95% complete. (There's some edge cases he's still sorting out, but all of the typical cases should work already.) Most of the design work comes from Levi Morrison and Paul Crovella. I helped out with the tests, a few edge bits, and general instigator/nudge. :-)
Discuss.
This is super nice! I can imagine many ways I would use it.
Some questions / thoughts.
- Serializing:
Will the partial function be serializable?
I assume if the original function is anonymous, then neither the
original nor the partial will be serializable.
Also if any of the fixed parameter values is not serializable, the
partial function can't be either.
But outside of the above cases it should be technically possible, or not?
-
var_export()
Calling var_export()
on a partial function could produce the original
code that generated the function.
E.g.
var_export(foo(?), TRUE) === 'foo(?)'
- Parameter switching
Could we express the following as a partial function?
static function ($a, $b) {return foo($b, $a);}
E.g. as foo(?1, ?0) ?
Perhaps it doesn't need to be part of this RFC, but it is something to consider.
-- Andreas
On Thu, May 13, 2021 at 2:31 PM Andreas Hennings andreas@dqxtech.net
wrote:
- Serializing:
But outside of the above cases it should be technically possible, or not?
I suspect that the limitations you cite make any kind of general
serializing both unreliable and impractical. I wouldn't plan on meaningful
serialization.
var_export()
Same as above, really.
- Parameter switching
Could we express the following as a partial function?
static function ($a, $b) {return foo($b, $a);}
E.g. as foo(?1, ?0) ?
Using named parameter we can:
foo(b: ?, a: ?);
-Sara
On Thu, May 13, 2021 at 2:31 PM Andreas Hennings andreas@dqxtech.net
wrote:
- Serializing:
But outside of the above cases it should be technically possible, or not?I suspect that the limitations you cite make any kind of general
serializing both unreliable and impractical. I wouldn't plan on meaningful
serialization.
var_export()
Same as above, really.
- Parameter switching
Could we express the following as a partial function?
static function ($a, $b) {return foo($b, $a);}
E.g. as foo(?1, ?0) ?
Using named parameter we can:
foo(b: ?, a: ?);
-Sara
The current implementation doesn't support re-ordering anything.
This feature has come up a few times. Personally, I don't think
partials need to support this and every other potential feature; at
some point a regular or short closure should be used.
As a reminder, a ?
does not create a single argument in the
closure's signature. All these $partial
s are the same, and here I do
not mean equivalent but literally the same:
function f($x, $y) {}
$partial = f(?);
$partial = f(?, ?);
$partial = f(?, ?, ?);
$partial = f(?, ?, ?, ?);
$partial = f(?, ?, ?, ?, ?);
On Thu, May 13, 2021 at 2:31 PM Andreas Hennings andreas@dqxtech.net
wrote:
- Serializing:
But outside of the above cases it should be technically possible, or not?I suspect that the limitations you cite make any kind of general
serializing both unreliable and impractical. I wouldn't plan on meaningful
serialization.
Correct. A partial produces a Closure object like any other now, so it's as serializable as any other Closure object. That is to say, not at all.
--Larry Garfield
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
https://wiki.php.net/rfc/partial_function_application
It includes an implementation by Joe Watkins that is already about 95% complete. (There's some edge cases he's still sorting out, but all of the typical cases should work already.) Most of the design work comes from Levi Morrison and Paul Crovella. I helped out with the tests, a few edge bits, and general instigator/nudge. :-)
Discuss.
Regarding "Comparison to other languages" and "Syntax choices" - I
lifted the design and ? character directly from XQuery, adapting for
PHP's variadic nature.
Regarding "Comparison to other languages"
Speaking of that section, there's one minor nitpick. Currently it says
Perl 6 was renamed to Racket, but it was actually renamed to Raku.
Racket is an unrelated, Lisp-like language.
--
Best regards,
Bruce Weirdan mailto:weirdan@gmail.com
Discuss.
There has been intense discussion in R11 about how the ? token applies
to missing or variadic arguments.
The root of the concern is that in the current proposal, ? applies as
either a placeholder for a single replacement, or 0-or-more varadic
arguments, depending on its position and the number of arguments.
I and several others believe that this feature would be better served by
restricting ? to representing a single argument, and using ...? to
represent "anything else" including an empty set.
To illustrate the scope for confusion:
foo(?) looks like it requires one argument, when in reality it could
accept none, 1, or a hundred.
foo(1, ?) looks like it also accepts one argument, but it could accept
0, or 100, depending on the function it is wrapping.
foo(?, 1) definitely requires at least one argument, but could accept
more depending on what it's wrapping.
Suggestion
Based on discussions in R11 there seems to be broad agreement that ?
should represent a single argument, and ...? should represent everything
else (including no arguments).
Thus:
foo(...?) - Partial of foo with all the arguments copied over.
foo(?, 1, ..?) - 1 required, then 1 fixed, 0 or more others are copied over.
foo(1, ...?) - Fix the first parameter and copy over the rest.
This can be seen as a mirror to the ... operator that accepts a variable
number of arguments.
function foo($a, ...$b) { }
Equally it matches the syntax for unpacking into arguments:
$a = foo($a, ...$b);
Passing more arguments than the partial defines would result in an
argument count error.
--
Mark Randall
Passing more arguments than the partial defines would result in an argument count error.
I think it’s reasonable to allow passing more arguments to a partial since user-defined functions and closures allow this without error.
Whether or not the extra arguments are automatically forwarded to the function wrapped by the partial is debatable.
Consider function foo(int $x, int $y, int $z) {}
with a partial defined as $partial = foo(?, 42)
.
If the partial is called as $partial(73, 8)
, should 8 be forwarded to $z
or should the call error as providing too few arguments? Or perhaps should the partial declaration should error, as it should have been foo(?, 42, ?)
or `foo(?, 42, ...?) so the partial provided all required arguments to foo.
Cheers,
Aaron Piotrowski
I think it’s reasonable to allow passing more arguments to a partial since user-defined functions and closures allow this without error.
But only userland functions, a relic from when func_get_args was the
only way to handle varaible numbers of arguments.
The documentation officially discourages func_get_args in favour of ...$
so I can definitely forsee the option of us deprecating that mechanism
sometime in 8.x and and removing it in 9.0.
I don't think it likely that it would go the other way of allowing
unlimited arguments to internal functions.
That being the case, IMO it makes more sense to introduce partials with
the same behaviour as internal functions, passing more functions than
specified (including partial arguments) should error.
Consider
function foo(int $x, int $y, int $z) {}
with a partial defined as$partial = foo(?, 42)
.If the partial is called as
$partial(73, 8)
, should 8 be forwarded to$z
or should the call error as providing too few arguments?
The 8 passes along to $z. There is no error, all required arguments
have been provided.
Or perhaps should the partial declaration should error, as it should have been
foo(?, 42, ?)
or `foo(?, 42, ...?) so the partial provided all required arguments to foo.
I think this highlights where the misunderstanding of this feature is.
Partial application is about binding arguments. ? isn't an argument,
it's an argument placeholder. It does two things: signals to create a
closure wrapping the function rather than calling it immediately, and
holds a position in the argument list so that an argument further to
the right can be fixed (bound) at that time. Arguments are bound;
argument placeholders are not, they exist only for convenience. The
syntax foo(?, 42)
doesn't call foo, let alone provide any arguments
to it, it simply creates a closure that'll pass along 42 at the
appropriate argument position along with whatever else it's provided
with.
Requiring additional trailing argument placeholders or adding an
additional token ...?
unnecessarily complicates things, burdens the
user, and only serves to further promote misunderstanding.
Cheers,
Aaron Piotrowski--
To unsubscribe, visit: https://www.php.net/unsub.php
Consider
function foo(int $x, int $y, int $z) {}
with a partial defined as$partial = foo(?, 42)
.If the partial is called as
$partial(73, 8)
, should 8 be forwarded to$z
or should the call error as providing too few arguments?The 8 passes along to $z. There is no error, all required arguments
have been provided.
In the current proposal, yes. In a hypothetical implementation where ? represented a single argument, I was asking what made sense. In that situation, I think 8 passing along still makes sense.
Or perhaps should the partial declaration should error, as it should have been
foo(?, 42, ?)
or `foo(?, 42, ...?) so the partial provided all required arguments to foo.I think this highlights where the misunderstanding of this feature is.
Partial application is about binding arguments. ? isn't an argument,
it's an argument placeholder. It does two things: signals to create a
closure wrapping the function rather than calling it immediately, and
holds a position in the argument list so that an argument further to
the right can be fixed (bound) at that time. Arguments are bound;
argument placeholders are not, they exist only for convenience. The
syntaxfoo(?, 42)
doesn't call foo, let alone provide any arguments
to it, it simply creates a closure that'll pass along 42 at the
appropriate argument position along with whatever else it's provided
with.Requiring additional trailing argument placeholders or adding an
additional token...?
unnecessarily complicates things, burdens the
user, and only serves to further promote misunderstanding.
My issue is the dual-meaning of ? in the current proposal. In foo(?, 42)
, the ? represents a single argument, but adding a trailing ? (such as in foo(?, 42, ?)
) represents any number of arguments. Would it perhaps make sense to make superfluous ? markers an error?
foo(?); // Fine, needed to define a partial with no bound args.
foo(?, 42); // Ok, binds second arg.
foo(?, ?, 42); // Ok, binds third arg.
foo(?, 42, ?); // Error, unnecessary placeholder.
foo(?, ?); // Error, unnecessary placeholder.
The intention here is to keep the syntax unambiguous.
foo(?) == foo(?, ?) == foo(?, ?, ?) and so forth is not going to be obvious to everyone, so why allow meaningless and misleading syntax.
Cheers,
Aaron Piotrowski
Mark Randall:
Based on discussions in R11 there seems to be broad agreement that ?
should represent a single argument, and ...? should represent everything
else (including no arguments).
This is incorrect. There were a few people arguing for that, but there was far from a consensus on that point. None of the RFC authors were convinced of the arguments given, for example.
Or perhaps should the partial declaration should error, as it should have been
foo(?, 42, ?)
or `foo(?, 42, ...?) so the partial provided all required arguments to foo.I think this highlights where the misunderstanding of this feature is.
Partial application is about binding arguments. ? isn't an argument,
it's an argument placeholder. It does two things: signals to create a
closure wrapping the function rather than calling it immediately, and
holds a position in the argument list so that an argument further to
the right can be fixed (bound) at that time. Arguments are bound;
argument placeholders are not, they exist only for convenience. The
syntaxfoo(?, 42)
doesn't call foo, let alone provide any arguments
to it, it simply creates a closure that'll pass along 42 at the
appropriate argument position along with whatever else it's provided
with.Requiring additional trailing argument placeholders or adding an
additional token...?
unnecessarily complicates things, burdens the
user, and only serves to further promote misunderstanding.My issue is the dual-meaning of ? in the current proposal. In
foo(?, 42)
, the ? represents a single argument, but adding a trailing ? (such
as infoo(?, 42, ?)
) represents any number of arguments. Would it
perhaps make sense to make superfluous ? markers an error?foo(?); // Fine, needed to define a partial with no bound args.
foo(?, 42); // Ok, binds second arg.
foo(?, ?, 42); // Ok, binds third arg.
foo(?, 42, ?); // Error, unnecessary placeholder.
foo(?, ?); // Error, unnecessary placeholder.The intention here is to keep the syntax unambiguous.
foo(?) == foo(?, ?) == foo(?, ?, ?) and so forth is not going to be
obvious to everyone, so why allow meaningless and misleading syntax.
Is that actually going to come up? Given that PHP functions (at least user-space ones) accept extra trailing arguments and just let them fall off, I would expect a closure that way to do the same. Named arguments continue that, I believe, by just ignoring any variadic arguments that do not match a parameter in the function. It seems odd to go back on that behavior now.
I can't speak for the others, but I could tolerate making "more than one extra ? beyond the end of the parameter list is an error", potentially, as at that point they're redundant. But if a function has, say, 4 params, then fourParams(1, 3, ?) is a convenient way to say "and placeholder everything else". Especially in dynamic cases like Nicolas pointed out, you may not necessarily know how many arguments there are.
As Paul noted above, this isn't a syntax for calling a function; it's effectively an even-shorter-hand way to write a short lambda. The rest of the closure construction is derived from the function being partially applied.
--Larry Garfield
I can't speak for the others, but I could tolerate making "more than one extra ? beyond the end of the parameter list is an error", potentially, as at that point they're redundant. But if a function has, say, 4 params, then fourParams(1, 3, ?) is a convenient way to say "and placeholder everything else".
Fortunately, we already have an existing and recognised way of saying
"and everything else"
https://www.php.net/manua/en/functions.arguments.php#functions.variablearg-list
Is that actually going to come up? Given that PHP functions (at least user-space ones) accept extra trailing arguments and just let them fall off, I would expect a closure that way to do the same. Named arguments continue that, I believe, by just ignoring any variadic arguments that do not match a parameter in the function. It seems odd to go back on that behavior now.
I don't consider forwarding extra arguments an issue. I briefly was thinking it might be nice to be explicit about the number of arguments a partial would accept, but you convinced me otherwise in R11, so I think we're on the same page here.
I can't speak for the others, but I could tolerate making "more than one extra ? beyond the end of the parameter list is an error", potentially, as at that point they're redundant. But if a function has, say, 4 params, then fourParams(1, 3, ?) is a convenient way to say "and placeholder everything else". Especially in dynamic cases like Nicolas pointed out, you may not necessarily know how many arguments there are.
With what I proposed in my last email, fourParams(1, 3, ?)
is acceptable, there's nothing superfluous there. At least one ? is needed to declare a partial. Similarly, a partial for a no parameter function: $partial = functionTakingNoParams(?)
. Or even a partial with args bound to all four params: fourParams(1, 2, 3, 4, ?)
.
What would error is fourParams(1, 3, ?, ?)
, as the second ? is meaningless.
I think you've convinced me that one-for-one matching on ? is burdensome, but the above is a happy medium perhaps?
Cheers,
Aaron Piotrowski
Is that actually going to come up? Given that PHP functions (at least user-space ones) accept extra trailing arguments and just let them fall off, I would expect a closure that way to do the same. Named arguments continue that, I believe, by just ignoring any variadic arguments that do not match a parameter in the function. It seems odd to go back on that behavior now.
I don't consider forwarding extra arguments an issue. I briefly was
thinking it might be nice to be explicit about the number of arguments
a partial would accept, but you convinced me otherwise in R11, so I
think we're on the same page here.I can't speak for the others, but I could tolerate making "more than one extra ? beyond the end of the parameter list is an error", potentially, as at that point they're redundant. But if a function has, say, 4 params, then fourParams(1, 3, ?) is a convenient way to say "and placeholder everything else". Especially in dynamic cases like Nicolas pointed out, you may not necessarily know how many arguments there are.
With what I proposed in my last email,
fourParams(1, 3, ?)
is
acceptable, there's nothing superfluous there. At least one ? is needed
to declare a partial. Similarly, a partial for a no parameter function:
$partial = functionTakingNoParams(?)
. Or even a partial with args
bound to all four params:fourParams(1, 2, 3, 4, ?)
.What would error is
fourParams(1, 3, ?, ?)
, as the second ? is meaningless.I think you've convinced me that one-for-one matching on ? is
burdensome, but the above is a happy medium perhaps?
I'd be OK with "no more than one trailing ? in excess of what the underlying callable has." (Which means if you don't know, just stick one ? at the end and you know it will work.)
--Larry Garfield
Is that actually going to come up? Given that PHP functions (at least user-space ones) accept extra trailing arguments and just let them fall off, I would expect a closure that way to do the same. Named arguments continue that, I believe, by just ignoring any variadic arguments that do not match a parameter in the function. It seems odd to go back on that behavior now.
I don't consider forwarding extra arguments an issue. I briefly was
thinking it might be nice to be explicit about the number of arguments
a partial would accept, but you convinced me otherwise in R11, so I
think we're on the same page here.I can't speak for the others, but I could tolerate making "more than one extra ? beyond the end of the parameter list is an error", potentially, as at that point they're redundant. But if a function has, say, 4 params, then fourParams(1, 3, ?) is a convenient way to say "and placeholder everything else". Especially in dynamic cases like Nicolas pointed out, you may not necessarily know how many arguments there are.
With what I proposed in my last email,
fourParams(1, 3, ?)
is
acceptable, there's nothing superfluous there. At least one ? is needed
to declare a partial. Similarly, a partial for a no parameter function:
$partial = functionTakingNoParams(?)
. Or even a partial with args
bound to all four params:fourParams(1, 2, 3, 4, ?)
.What would error is
fourParams(1, 3, ?, ?)
, as the second ? is meaningless.I think you've convinced me that one-for-one matching on ? is
burdensome, but the above is a happy medium perhaps?I'd be OK with "no more than one trailing ? in excess of what the underlying callable has." (Which means if you don't know, just stick one ? at the end and you know it will work.)
I think multiple trailing ? should be an error, otherwise how am I suppose to know at a glance if a partial declaration will error? Plus it’s adding multiple ways to declare the same thing, which I was hoping to avoid.
fourParams(1, 2, ?); // OK
fourParams(1, 2, ?, ?); // OK for you, should error to me
fourParams(1, 2, ?, ?, ?); // Again OK for you, should error to me
fourParams(1, 2, ?, ?, ?, ?); // Error for both
What value is gained in allowing any of those but the first?
I’d also be fine allowing a trailing ? in any declaration. It’s unnecessary, but one could argue that it’s consistent to allow a trailing ? in any partial, since it’s required for some.
fourParams(?, 2, ?); // Could error, but probably fine for consistency
Aaron Piotrowski
My issue is the dual-meaning of ? in the current proposal. In
foo(?, 42)
, the ? represents a single argument, but adding a trailing ? (such as infoo(?, 42, ?)
) represents any number of arguments. Would it perhaps make sense to make superfluous ? markers an error?foo(?); // Fine, needed to define a partial with no bound args.
foo(?, 42); // Ok, binds second arg.
foo(?, ?, 42); // Ok, binds third arg.
foo(?, 42, ?); // Error, unnecessary placeholder.
foo(?, ?); // Error, unnecessary placeholder.The intention here is to keep the syntax unambiguous.
foo(?) == foo(?, ?) == foo(?, ?, ?) and so forth is not going to be obvious to everyone, so why allow meaningless and misleading syntax.
Cheers,
Aaron Piotrowski
While it's my preference not to use superfluous placeholders they do
no real harm and I do not feel comfortable imposing this preference on
others.
My issue is the dual-meaning of ? in the current proposal. In
foo(?, 42)
, the ? represents a single argument, but adding a trailing ? (such as infoo(?, 42, ?)
) represents any number of arguments. Would it perhaps make sense to make superfluous ? markers an error?foo(?); // Fine, needed to define a partial with no bound args.
foo(?, 42); // Ok, binds second arg.
foo(?, ?, 42); // Ok, binds third arg.
foo(?, 42, ?); // Error, unnecessary placeholder.
foo(?, ?); // Error, unnecessary placeholder.The intention here is to keep the syntax unambiguous.
foo(?) == foo(?, ?) == foo(?, ?, ?) and so forth is not going to be obvious to everyone, so why allow meaningless and misleading syntax.
Cheers,
Aaron PiotrowskiWhile it's my preference not to use superfluous placeholders they do
no real harm and I do not feel comfortable imposing this preference on
others.
User-space functions have always accepted more arguments than they're defined with. They just get dropped off the end silently, unless you use func_get_args()
or variadics. While I know not everyone likes that "feature", it means that extra trailing ? "arguments" don't feel weird to me. They just get dropped off the end and we move on with life. At least that's how I conceptualize them.
--Larry Garfield
On Tue, May 18, 2021 at 2:45 PM Larry Garfield larry@garfieldtech.com
wrote:
User-space functions have always accepted more arguments than they're
defined with. They just get dropped off the end silently, unless you use
func_get_args()
or variadics. While I know not everyone likes that
"feature", it means that extra trailing ? "arguments" don't feel weird to
me. They just get dropped off the end and we move on with life. At least
that's how I conceptualize them.
This is my question about the partials feature, though; does the
implementation have potential to reasonably be confusing? if it's
essentially a convenient way of creating a closure, why would extra
arguments passed in the closure call be passed to the wrapped function,
rather than discarded?
function foo(int $a, int $b, int ...$p) { ... }
$partial = foo(?, 10);
$partial(5, 15, 25);
Intuitively, because the existing convention is extra unused parameters in
user defined functions are silently ignored, I think I would expect the
above to be equivalent to something like:
$partial = fn(int $a) => foo($a, 10);
$partial(5, 15, 25); // 15 and 25 are lopped off to no effect
and not
$partial = fn(int $a, int ...$params) => foo($a, 10, ....$params);
But correct me if I'm wrong, isn't the latter what the RFC effectively
proposes?
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi David,
function foo(int $a, int $b, int ...$p) { ... }
$partial = foo(?, 10);$partial(5, 15, 25);
Intuitively, because the existing convention is extra unused parameters in
user defined functions are silently ignored, I think I would expect the
above to be equivalent to something like:$partial = fn(int $a) => foo($a, 10);
$partial(5, 15, 25); // 15 and 25 are lopped off to no effectand not
$partial = fn(int $a, int ...$params) => foo($a, 10, ....$params);
Did you see my message yesterday about the two mental models of the
feature? https://externals.io/message/114157#114492
Your expectation there is in line with the "start with an empty closure
and add things" model; the behaviour proposed in the RFC is in line with
the "copy the full signature and then splice in fixed values" model.
I do worry that users of the language will assume the "wrong" mental
model, though, unless we pick a syntax that more clearly matches the
"copy and splice" model. I think that would mean having a way to say
"make this is a partial", and a way to indicate which arguments to
"splice", without any "placeholders".
Yet Another Bad Unsolicited Syntax Suggestion:
$partial = partial foo(); // no bindings; current RFC $partial = foo(?);
$partial = partial foo(b: 10); // bind named parameter $b; current RFC
$partial = foo(?, b: 10);
$partial = partial foo(1: 10); // bind positional parameter #1
(zero-indexed); current RFC $partial = foo(?, 10);
Regards,
--
Rowan Tommins
[IMSoP]
On Tue, May 18, 2021 at 9:51 PM Rowan Tommins rowan.collins@gmail.com
wrote:
Hi David,
Did you see my message yesterday about the two mental models of the
feature? https://externals.io/message/114157#114492Your expectation there is in line with the "start with an empty closure
and add things" model; the behaviour proposed in the RFC is in line with
the "copy the full signature and then splice in fixed values" model.
Well this is the crux of the issue for me, yes; I think the latter model is
arguably more useful, I think the former model is what the proposed syntax
implies and the former model is therefore the more natural and "readable"
interpretation of what users might expect partials to mean when we're
looking at hypothetical userland code built on this feature. I can't help
but question the value of introducing something which has the potential to
be ambiguous and misinterpreted - and I think the discussion in this thread
has highlighted that there is ambiguity.
Further, the RFC as it stands seems to introduce the possibility of writing
ambiguous code when defining a partial. My intuition is that most PHP users
would expect a single placeholder character (?) to represent one and
exactly one parameter, analogous to SQL. But there are other ways you might
reasonably interpret and argue for the syntax, which I think have been
well-expressed by Larry and others.
I don't know what the answer is here, I'm reading through the entire thread
and RFC again and trying to better formulate in my mind how I'd [like to]
reason about this in terms of syntax. But the more I think about it, the
more I realise much as I loved the idea when I first read the RFC, now I'm
worried we're rushing to include something (because there's an
implementation, more or less ready to go) which maybe should go back to the
drawing board for a bit of a re-think.
Maybe I'm wrong, I don't know, but yeah...I have reservations now. If I had
a vote and voting was open at this point, I think my inclination would be
to -1 the RFC as it currently stands, not because it isn't a good idea but
because right now, it will definitely confuse some significant proportion
of users.
I do worry that users of the language will assume the "wrong" mental
model, though, unless we pick a syntax that more clearly matches the
"copy and splice" model. I think that would mean having a way to say
"make this is a partial", and a way to indicate which arguments to
"splice", without any "placeholders".Yet Another Bad Unsolicited Syntax Suggestion:
$partial = partial foo(); // no bindings; current RFC $partial = foo(?);
$partial = partial foo(b: 10); // bind named parameter $b; current RFC
$partial = foo(?, b: 10);
$partial = partial foo(1: 10); // bind positional parameter #1
(zero-indexed); current RFC $partial = foo(?, 10);Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
I don't know what the answer is here, I'm reading through the entire thread
and RFC again and trying to better formulate in my mind how I'd [like to]
reason about this in terms of syntax. But the more I think about it, the
more I realise much as I loved the idea when I first read the RFC, now I'm
worried we're rushing to include something (because there's an
implementation, more or less ready to go) which maybe should go back to the
drawing board for a bit of a re-think.
Some of the RFC authors have been discussing this a lot based on
feedback. In a previous message I said we would message the list
when it's ready for re-review. It's still not ready, but we seem to be
driving towards consensus.
I wish everyone would hold the discussion until then because they are
discussing an outdated thing and causing noise, but it's not something
I can control.
On Tue, May 18, 2021 at 11:02 PM Levi Morrison levi.morrison@datadoghq.com
wrote:
Some of the RFC authors have been discussing this a lot based on
feedback. In a [previous message][1] I said we would message the list
when it's ready for re-review. It's still not ready, but we seem to be
driving towards consensus.
Okay, great. I thought your previous message only referenced named
placeholders, I didn't take from that you were also considering other
discussion points. Look forward to seeing the updated RFC; personally I
think the meat of it is a good addition to the language, all the sweeter if
it's able to land for 8.1.
Requiring additional trailing argument placeholders or adding an
additional token...?
unnecessarily complicates things, burdens the
user, and only serves to further promote misunderstanding.Providing ? as a means of placeholder for some arguments and ignoring the
rest could complicate the readability in my opinion.
Maybe we should move (?) out of the arguments list as a means of creating a
partial.
What I realized is that we need a way of signaling partial function
creation. Be it foo(?) or foo(?, ?, ?) or however many ? is added there,
which does not convey any meaning and also doesn't produce any sort of
error!
Say instead of foo(?) we had ?foo().
Since we have named parameters it could help us in providing some of the
arguments and deferring the rest.
For instance:
function foo($x, $y, ...$z) {}
?foo(); // causes a partial
?foo(y: '..'); // also causes a partial
This way we wouldn't need to worry about the number of ? added to arguments
list.
It may also help in avoiding future PSRs in telling us how many ? we should
put in there :)
On Sat, 15 May 2021 at 09:03, Hossein Baghayi hossein.baghayi@gmail.com
wrote:
Providing ? as a means of placeholder for some arguments and ignoring the
rest could complicate the readability in my opinion.
Maybe we should move (?) out of the arguments list as a means of creating
a partial.What I realized is that we need a way of signaling partial function
creation. Be it foo(?) or foo(?, ?, ?) or however many ? is added there,
which does not convey any meaning and also doesn't produce any sort of
error!Say instead of foo(?) we had ?foo().
Since we have named parameters it could help us in providing some of the
arguments and deferring the rest.For instance:
function foo($x, $y, ...$z) {} ?foo(); // causes a partial ?foo(y: '..'); // also causes a partial
This way we wouldn't need to worry about the number of ? added to
arguments list.
It may also help in avoiding future PSRs in telling us how many ? we
should put in there :)
In addition to these, I was thinking of 2 other cases in which changing the
current proposal might be helpful.
1- When there is a parameterless function (an expensive operation maybe).
2- When all parameters are passed but the function is not expected to be
called yet.
In the case of a parameterless function, maybe it is an expensive function
call and we need to defer calling it.
Maybe all we need is to hold a reference to it and pass it around?
With the current proposal, I do not know if it is possible or not. Since
there are no parameters defined.
function with_expensive_operations_lurking_inside() {...}
Can we or should we call this function this way:
$ref = with_expensive_operations_lurking_inside(?);
It feels odd having to provide parameters when there is none needed.
For the other use case where all parameters are passed but is not expected
to be called yet:
Maybe providing parameters and setting it up is not our responsibility.
function expensive_or_not($a, $b, $c) {...}
$do_not_get_called_please = expensive_or_not(1, 2, 3, ?); // with an extra
parameter as to mark as partial!
$do_not_get_called_please = expensive_or_not(?, 1, 2, 3); // or maybe this
way?!
Well, I was thinking that by changing the proposed syntax we could achieve
what is proposed and a little bit more.
Also we wouldn't need to worry about the number of ? needed as arguments.
Since all we need is to mark the return type as partial (closure) on the
fly and grab hold of what is passed as arguments.
There are some different syntaxes that come to my mind:
We could still use ? but outside of the arguments list?
$partial = xyx?(..);
$partial = ?xyx(..);
or maybe different symbols:
$partial = :xyz(..);
We might be able to even cast the return type:
$partial = (?) xyz(..);
$partial = (partial) xyz(..);
$partial = (fn) xyz(..);
On Sat, 15 May 2021 at 09:03, Hossein Baghayi hossein.baghayi@gmail.com
wrote:Providing ? as a means of placeholder for some arguments and ignoring the
rest could complicate the readability in my opinion.
Maybe we should move (?) out of the arguments list as a means of creating
a partial.What I realized is that we need a way of signaling partial function
creation. Be it foo(?) or foo(?, ?, ?) or however many ? is added there,
which does not convey any meaning and also doesn't produce any sort of
error!Say instead of foo(?) we had ?foo().
Since we have named parameters it could help us in providing some of the
arguments and deferring the rest.For instance:
function foo($x, $y, ...$z) {} ?foo(); // causes a partial ?foo(y: '..'); // also causes a partial
This way we wouldn't need to worry about the number of ? added to
arguments list.
It may also help in avoiding future PSRs in telling us how many ? we
should put in there :)In addition to these, I was thinking of 2 other cases in which changing the
current proposal might be helpful.1- When there is a parameterless function (an expensive operation maybe).
2- When all parameters are passed but the function is not expected to be
called yet.In the case of a parameterless function, maybe it is an expensive function
call and we need to defer calling it.
Maybe all we need is to hold a reference to it and pass it around?
With the current proposal, I do not know if it is possible or not. Since
there are no parameters defined.function with_expensive_operations_lurking_inside() {...}
Can we or should we call this function this way:
$ref = with_expensive_operations_lurking_inside(?);
It feels odd having to provide parameters when there is none needed.
For the other use case where all parameters are passed but is not expected
to be called yet:
Maybe providing parameters and setting it up is not our responsibility.function expensive_or_not($a, $b, $c) {...} $do_not_get_called_please = expensive_or_not(1, 2, 3, ?); // with an extra parameter as to mark as partial! $do_not_get_called_please = expensive_or_not(?, 1, 2, 3); // or maybe this way?!
Well, I was thinking that by changing the proposed syntax we could achieve
what is proposed and a little bit more.
Also we wouldn't need to worry about the number of ? needed as arguments.
Since all we need is to mark the return type as partial (closure) on the
fly and grab hold of what is passed as arguments.There are some different syntaxes that come to my mind:
We could still use ? but outside of the arguments list?$partial = xyx?(..); $partial = ?xyx(..);
or maybe different symbols:
$partial = :xyz(..);
We might be able to even cast the return type:
$partial = (?) xyz(..); $partial = (partial) xyz(..); $partial = (fn) xyz(..);
Casting is another interesting approach that does feel more consistent with the existing language.
Since it is creating a closure, wouldn't this make the most sense?
$partial = (closure) abc();
$partial = (closure) xyz(?,24);
-Mike
On May 16, 2021, at 10:43 PM, Hossein Baghayi hossein.baghayi@gmail.com
wrote:On Sat, 15 May 2021 at 09:03, Hossein Baghayi <hossein.baghayi@gmail.com
wrote:
Providing ? as a means of placeholder for some arguments and ignoring
the
rest could complicate the readability in my opinion.
Maybe we should move (?) out of the arguments list as a means of
creating
a partial.What I realized is that we need a way of signaling partial function
creation. Be it foo(?) or foo(?, ?, ?) or however many ? is added there,
which does not convey any meaning and also doesn't produce any sort of
error!Say instead of foo(?) we had ?foo().
Since we have named parameters it could help us in providing some of the
arguments and deferring the rest.For instance:
function foo($x, $y, ...$z) {} ?foo(); // causes a partial ?foo(y: '..'); // also causes a partial
This way we wouldn't need to worry about the number of ? added to
arguments list.
It may also help in avoiding future PSRs in telling us how many ? we
should put in there :)In addition to these, I was thinking of 2 other cases in which changing
the
current proposal might be helpful.1- When there is a parameterless function (an expensive operation maybe).
2- When all parameters are passed but the function is not expected to be
called yet.In the case of a parameterless function, maybe it is an expensive
function
call and we need to defer calling it.
Maybe all we need is to hold a reference to it and pass it around?
With the current proposal, I do not know if it is possible or not. Since
there are no parameters defined.function with_expensive_operations_lurking_inside() {...}
Can we or should we call this function this way:
$ref = with_expensive_operations_lurking_inside(?);
It feels odd having to provide parameters when there is none needed.
For the other use case where all parameters are passed but is not
expected
to be called yet:
Maybe providing parameters and setting it up is not our responsibility.function expensive_or_not($a, $b, $c) {...} $do_not_get_called_please = expensive_or_not(1, 2, 3, ?); // with an
extra
parameter as to mark as partial!
$do_not_get_called_please = expensive_or_not(?, 1, 2, 3); // or maybe
this
way?!Well, I was thinking that by changing the proposed syntax we could
achieve
what is proposed and a little bit more.
Also we wouldn't need to worry about the number of ? needed as arguments.
Since all we need is to mark the return type as partial (closure) on the
fly and grab hold of what is passed as arguments.There are some different syntaxes that come to my mind:
We could still use ? but outside of the arguments list?$partial = xyx?(..); $partial = ?xyx(..);
or maybe different symbols:
$partial = :xyz(..);
We might be able to even cast the return type:
$partial = (?) xyz(..); $partial = (partial) xyz(..); $partial = (fn) xyz(..);
Casting is another interesting approach that does feel more consistent
with the existing language.Since it is creating a closure, wouldn't this make the most sense?
$partial = (closure) abc();
Ouch! definitely NOT!
$partial = (closure) xyz(?,24);
-Mike
--
To unsubscribe, visit: https://www.php.net/unsub.php
But I agree that these two cases "don't feel quite right":
function noParam() {/*...*/}
$p = noParam(?); /* looks like there's 1 param, to be passed on call */
function threeParams($a, $b, $c) {/*...*/}
$p = threeParams(1, 2, 3, ?); /* looks like there's 4 params, with the last
one to be passed on call */
(both would be called as $p()
, i.e. without any arg). Here we had to add
a ?
only for "technical" reasons.
More generally I also agree that ?
having two different
meanings/behaviors in e.g. f(?, 2)
[or f(1, ?, 3)
] vs f(1, ?)
--
namely: "exactly 1 arg" vs "0+ args" -- is confusing.
I think I too would prefer ?
to only mean "exactly 1 arg", and
- either have another token for "0+ args" (e.g.:
f(?, 2)
[andf(1, ?, 3)
] vsf(1, ...)
, and alsonoParam(...)
andthreeParams(1, 2, 3, ...)
) - or have another syntax like Hossein first suggested (e.g.:
*f(?, 2)
[and*f(1, ?, 3)
] vs*f(1)
, and also*noParam()
and*threeParams(1, 2, 3)
).
Unless there are compelling (or at least convincing) reasons against?
Thanks,
--
Guilliam Xavier
Joe Watkins has successfully worked out some bugs that also enable
more powerful behavior. Thanks to Nikita and any others who worked
with Joe on fixing these issues and reviewing the PR.
The short of it is that re-ordering parameters when using named
placeholders is now technically possible. We will send an update when
the RFC and implementation are ready for re-review.
On Mon, May 17, 2021 at 5:01 PM Levi Morrison levi.morrison@datadoghq.com
wrote:
Joe Watkins has successfully worked out some bugs that also enable
more powerful behavior. Thanks to Nikita and any others who worked
with Joe on fixing these issues and reviewing the PR.The short of it is that re-ordering parameters when using named
placeholders is now technically possible. We will send an update when
the RFC and implementation are ready for re-review.
That's great news!
By the way, I forgot to add that another syntax might also be clearer when
binding a value to a named parameter, e.g.:
function f($a, $b, $c, $d, $e) {/*...*/}
/* We want to bind value 4 to param "d" */
// with current syntax:
$p = f(?, d: 4); /* or f(?, ?, ?, 4) */
// with another hypothetical syntax:
$p = *f(d: 4);
Anyway, looking forward to your update =)
Thanks,
--
Guilliam Xavier
On Mon, May 17, 2021 at 6:36 PM Guilliam Xavier guilliam.xavier@gmail.com
wrote:
On Mon, May 17, 2021 at 5:01 PM Levi Morrison <levi.morrison@datadoghq.com
wrote:
Joe Watkins has successfully worked out some bugs that also enable
more powerful behavior. Thanks to Nikita and any others who worked
with Joe on fixing these issues and reviewing the PR.The short of it is that re-ordering parameters when using named
placeholders is now technically possible. We will send an update when
the RFC and implementation are ready for re-review.That's great news!
By the way, I forgot to add that another syntax might also be clearer when
binding a value to a named parameter, e.g.:function f($a, $b, $c, $d, $e) {/*...*/} /* We want to bind value 4 to param "d" */ // with current syntax: $p = f(?, d: 4); /* or f(?, ?, ?, 4) */ // with another hypothetical syntax: $p = *f(d: 4);
Anyway, looking forward to your update =)
Thanks,
--
Guilliam Xavier
Hey Guilliam,
adding a special character like * or ? before the function name is
not going to work.
It might look good for *f() but it has the same issues as casting.
You should be able to have with the current version a code like
$actionGenerator->getDriveAction()('home', ?)
You can't really put the * or ? before the function name here.
Maybe having it just before the parenthesis would work.
Regards,
Alex
On Mon, May 17, 2021 at 5:47 PM Alexandru Pătrănescu drealecs@gmail.com
wrote:
On Mon, May 17, 2021 at 6:36 PM Guilliam Xavier guilliam.xavier@gmail.com
wrote:On Mon, May 17, 2021 at 5:01 PM Levi Morrison <
levi.morrison@datadoghq.com>
wrote:Joe Watkins has successfully worked out some bugs that also enable
more powerful behavior. Thanks to Nikita and any others who worked
with Joe on fixing these issues and reviewing the PR.The short of it is that re-ordering parameters when using named
placeholders is now technically possible. We will send an update when
the RFC and implementation are ready for re-review.That's great news!
By the way, I forgot to add that another syntax might also be clearer when
binding a value to a named parameter, e.g.:function f($a, $b, $c, $d, $e) {/*...*/} /* We want to bind value 4 to param "d" */ // with current syntax: $p = f(?, d: 4); /* or f(?, ?, ?, 4) */ // with another hypothetical syntax: $p = *f(d: 4);
Anyway, looking forward to your update =)
Thanks,
--
Guilliam XavierHey Guilliam,
adding a special character like * or ? before the function name is
not going to work.
It might look good for *f() but it has the same issues as casting.You should be able to have with the current version a code like
$actionGenerator->getDriveAction()('home', ?)
You can't really put the * or ? before the function name here.Maybe having it just before the parenthesis would work.
Regards,
Alex
Hey Alex,
I was thinking of a (special) new unary [prefix] operator (e.g. *
which
currently only exists as binary) with a higher precedence than ()
(if
that makes sense), which indeed means that e.g. *f(1)(2)
would be like
current f(1, ?)(2)
, and f(1)(2, ?)
would require wrapping as
*(f(1))(2)
.
A lower precedence would require wrapping as (*f)(1)
for the simple case
(i.e. most of the time), which moreover would probably be problematic if
you have both a function f
and a const f
.
Just before the affected (
might also work indeed, but with another
symbol, as f(1)*(2)
[and f*(1)
] already means f(1) * 2
[and f * 1
]. Maybe !
, @
or ~
would be unambiguous at that position?
(For both, there's also the possibility of choosing not a symbol but a
keyword...)
Alternatively, f(..., d: 4)
or probably rather f(d: 4, ...)
would
arguably still be clearer than the current f(?, d: 4)
, but still wouldn't
really solve the "weirdness" of noParam(...)
and threeParams(1, 2, 3, ...)
.
Sorry, I didn't intend to diverge so much. The gist is that I (too) don't
like the current "duality" of ?
.
PS: when I write "current" I really mean "in the current state of the RFC".
Regards,
--
Guilliam Xavier
On Mon, May 17, 2021, 19:53 Guilliam Xavier guilliam.xavier@gmail.com
wrote:
On Mon, May 17, 2021 at 5:47 PM Alexandru Pătrănescu drealecs@gmail.com
wrote:On Mon, May 17, 2021 at 6:36 PM Guilliam Xavier <
guilliam.xavier@gmail.com> wrote:On Mon, May 17, 2021 at 5:01 PM Levi Morrison <
levi.morrison@datadoghq.com>
wrote:Joe Watkins has successfully worked out some bugs that also enable
more powerful behavior. Thanks to Nikita and any others who worked
with Joe on fixing these issues and reviewing the PR.The short of it is that re-ordering parameters when using named
placeholders is now technically possible. We will send an update when
the RFC and implementation are ready for re-review.That's great news!
By the way, I forgot to add that another syntax might also be clearer
when
binding a value to a named parameter, e.g.:function f($a, $b, $c, $d, $e) {/*...*/} /* We want to bind value 4 to param "d" */ // with current syntax: $p = f(?, d: 4); /* or f(?, ?, ?, 4) */ // with another hypothetical syntax: $p = *f(d: 4);
Anyway, looking forward to your update =)
Thanks,
--
Guilliam XavierHey Guilliam,
adding a special character like * or ? before the function name is
not going to work.
It might look good for *f() but it has the same issues as casting.You should be able to have with the current version a code like
$actionGenerator->getDriveAction()('home', ?)
You can't really put the * or ? before the function name here.Maybe having it just before the parenthesis would work.
Regards,
AlexHey Alex,
I was thinking of a (special) new unary [prefix] operator (e.g.
*
which
currently only exists as binary) with a higher precedence than()
(if
that makes sense), which indeed means that e.g.*f(1)(2)
would be like
currentf(1, ?)(2)
, andf(1)(2, ?)
would require wrapping as
*(f(1))(2)
.
A lower precedence would require wrapping as(*f)(1)
for the simple case
(i.e. most of the time), which moreover would probably be problematic if
you have both afunction f
and aconst f
.Just before the affected
(
might also work indeed, but with another
symbol, asf(1)*(2)
[andf*(1)
] already meansf(1) * 2
[andf * 1
]. Maybe!
,@
or~
would be unambiguous at that position?(For both, there's also the possibility of choosing not a symbol but a
keyword...)Alternatively,
f(..., d: 4)
or probably ratherf(d: 4, ...)
would
arguably still be clearer than the currentf(?, d: 4)
, but still wouldn't
really solve the "weirdness" ofnoParam(...)
andthreeParams(1, 2, 3, ...)
.Sorry, I didn't intend to diverge so much. The gist is that I (too) don't
like the current "duality" of?
.
Actually, the current form is not very troublesome to me.
In terms of partial function application, it is fine and it serves the
purpose good enough while having a simple form.
The issue is that in recent discussions, also considering that parameter
re-ordering issue is done, it appears that we can use it also for other use
cases like simple pass-through adaptors between two interfaces.
And because of that, I would like to have also a partial closure where the
extra parameters are not passed further. And that is currently not
possible. Whether this feature is considered out-of-scope for partials,
that's for RFC authors to decide.
Cheers,
Alex
Well, I was thinking that by changing the proposed syntax we could achieve
what is proposed and a little bit more.
Also we wouldn't need to worry about the number of ? needed as arguments.
Since all we need is to mark the return type as partial (closure) on the
fly and grab hold of what is passed as arguments.There are some different syntaxes that come to my mind:
We could still use ? but outside of the arguments list?$partial = xyx?(..); $partial = ?xyx(..);
or maybe different symbols:
$partial = :xyz(..);
We might be able to even cast the return type:
$partial = (?) xyz(..); $partial = (partial) xyz(..); $partial = (fn) xyz(..);
Casting is another interesting approach that does feel more consistent with the existing language.
Since it is creating a closure, wouldn't this make the most sense?
$partial = (closure) abc();
Ouch! definitely NOT!
$partial = (closure) xyz(?,24);
Mind if I ask for you to elaborate on your aversion?
Asking for general understanding, not to debate the point.
-Mike
On May 17, 2021, at 10:50 AM, Guilliam Xavier guilliam.xavier@gmail.com
wrote:On Mon, May 17, 2021 at 6:58 AM Mike Schinkel <mike@newclarity.net
mailto:mike@newclarity.net> wrote:Casting is another interesting approach that does feel more consistent
with the existing language.Since it is creating a closure, wouldn't this make the most sense?
$partial = (closure) abc();
Ouch! definitely NOT!
$partial = (closure) xyz(?,24);
Mind if I ask for you to elaborate on your aversion?
Asking for general understanding, not to debate the point.
-Mike
Hey Mike,
Let me share something that's possible now and it would be pretty weird
with the casting syntax:
(function (...$params) { var_dump($params);})(?, 2)(1, ?)(?, 4)(?, ?,
6)(?, 5)(3, ?)(7);
Will pass the parameter 1, 2, 3, 4, 5, 6, 7 in order to the closure that
was initially defined.
I think casting requires extra grouping.
On May 17, 2021, at 10:50 AM, Guilliam Xavier guilliam.xavier@gmail.com
wrote:Well, I was thinking that by changing the proposed syntax we could
achieve
what is proposed and a little bit more.
Also we wouldn't need to worry about the number of ? needed as
arguments.
Since all we need is to mark the return type as partial (closure) on the
fly and grab hold of what is passed as arguments.There are some different syntaxes that come to my mind:
We could still use ? but outside of the arguments list?$partial = xyx?(..); $partial = ?xyx(..);
or maybe different symbols:
$partial = :xyz(..);
We might be able to even cast the return type:
$partial = (?) xyz(..); $partial = (partial) xyz(..); $partial = (fn) xyz(..);
Casting is another interesting approach that does feel more consistent
with the existing language.Since it is creating a closure, wouldn't this make the most sense?
$partial = (closure) abc();
Ouch! definitely NOT!
$partial = (closure) xyz(?,24);
Mind if I ask for you to elaborate on your aversion?
Asking for general understanding, not to debate the point.
-Mike
Sorry I was too "raw". I mean that the cast syntax $p = (whatever) f();
already very clearly means/does "evaluate f() [i.e. call it], then convert
the evaluation result to whatever, then assign the conversion result to
$p", and I think it would be a very bad idea to "reuse" the same syntax
for something that wouldn't call f() immediately.
I'm favorable to other syntaxes, but which don't look like a cast.
Regards,
--
Guilliam Xavier
I think this highlights where the misunderstanding of this feature is.
I think the fact that there is so much confusion highlights why it is worth considering different designs.
Partial application is about binding arguments. ? isn't an argument,
it's an argument placeholder. It does two things: signals to create a
closure wrapping the function rather than calling it immediately, and
holds a position in the argument list so that an argument further to
the right can be fixed (bound) at that time.
This is not a correct description of the current syntax. Currently, "?" represents a required argument in the argument list, but only if there is a fixed value to its right. If it appears at the end of the argument list, or with only other ? tokens to its right, it only signals that a partial should be created, and doesn't create a required argument, even though it looks the same.
foo(?, 42) creates a closure with one required argument; foo(42, ?) creates a closure with no required arguments
Requiring additional trailing argument placeholders or adding an
additional token...?
unnecessarily complicates things, burdens the
user, and only serves to further promote misunderstanding.
On the contrary, saying that "?" always means exactly one argument massively simplifies the description of the feature. Why persist with a version of the syntax that is so easy to misunderstand when we have a really simple fix available?
I acknowledge the need for a syntax to say "accept zero or more further arguments", but this doesn't need to overload the syntax for "create a required argument here".
If the suggestion of ...? is too long, we could look at other options like ... or ?? The syntax for "just make a closure and pass all arguments through" would then be "foo(...)" or "foo(??)".
There is a separate question of whether arguments that weren't required are passed along anyway. I'm less sure there's a right answer there, and would be happy with a version where foo(?, 42) and foo(?, 42, ...) were equivalent - that is, the trailing "all other arguments" token would only be needed if there wasn't already a placeholder.
Regards,
--
Rowan Tommins
[IMSoP]
I think this highlights where the misunderstanding of this feature is.
I think the fact that there is so much confusion highlights why it is worth considering different designs.
Exactly this.
I have been debating whether to comment on this thread, but seeing Rowen's comments mean I am not alone in my opinion so here goes.
I too have been bothered with the inconsistent usage of the single question mark ('?'). While it makes perfect sense once it is explained, it is possibly not intuitive for those who have learned how to use a command line shell where a single question mark indicates a single "thing" (where "thing" is a single character in the shell.)
Using it in PHP to mean "one, or more" seems like it would cause needless confusion given that intuition derived from command line experience might lead developers to be confused. Better to use something which would not be likely to lead developers astray I would think.
Partial application is about binding arguments. ? isn't an argument,
it's an argument placeholder. It does two things: signals to create a
closure wrapping the function rather than calling it immediately, and
holds a position in the argument list so that an argument further to
the right can be fixed (bound) at that time.This is not a correct description of the current syntax. Currently, "?" represents a required argument in the argument list, but only if there is a fixed value to its right. If it appears at the end of the argument list, or with only other ? tokens to its right, it only signals that a partial should be created, and doesn't create a required argument, even though it looks the same.
foo(?, 42) creates a closure with one required argument; foo(42, ?) creates a closure with no required arguments
Requiring additional trailing argument placeholders or adding an
additional token...?
unnecessarily complicates things, burdens the
user, and only serves to further promote misunderstanding.On the contrary, saying that "?" always means exactly one argument massively simplifies the description of the feature. Why persist with a version of the syntax that is so easy to misunderstand when we have a really simple fix available?
I acknowledge the need for a syntax to say "accept zero or more further arguments", but this doesn't need to overload the syntax for "create a required argument here".
If the suggestion of ...? is too long, we could look at other options like ... or ?? The syntax for "just make a closure and pass all arguments through" would then be "foo(...)" or "foo(??)".
Yes!
Since we need a sigil to indicate a function be wrapped in a closure I was actually planning to propose ??
so I was pleasantly surprised to see Rowan's suggestion. Ellipses would work too, but as I think ??
would be easier to see in dense code, so I would prefer them.
Which gives us::
function foo($a,$b,$c) {}
$x = foo(??); // Wrap foo() in a closure which expects 3 parameters
$x = foo(?, 24); // Wrap foo() in a closure which expects 2 parameters with 24
binding to $b
$x = foo(?, 24, ??); // Same as foo(?, 24);
There is a separate question of whether arguments that weren't required are passed along anyway. I'm less sure there's a right answer there, and would be happy with a version where foo(?, 42) and foo(?, 42, ...) were equivalent - that is, the trailing "all other arguments" token would only be needed if there wasn't already a placeholder.
Ditto, but with ??
preferred.
Said another way, I am arguing exactly what Rowan arged except that I have a preference for one sigil over the other.
-Mike
Greetings, Internalians!
I would like to offer for your consideration another RFC, specifically syntax for partial function application.
During off-list discussions, it's become clear that there are two
different ways of modelling what this feature does, which is leading to
a bit of misunderstanding and frustration (and some plain disagreement
on which is the better model). At risk of getting it wrong and
confusing things more, I'd like to attempt to describe the two models.
The first model is that a partial application starts with an empty
signature, and adds arguments to it based on placeholders. You could
picture the steps like this:
function foo(int $a, int $b, int $c) { /* whatever */ }
$partial = foo(?, ?, 42);
Step 1 - empty closure: $partial = fn() => foo();
Step 2 - 1st argument is a ? so copy that parameter from the original:
$partial = fn(int $a) => foo($a);
Step 3 - 2nd argument is a ? so copy from the original: $partial =
fn(int $a, int $b) => foo($a, $b);
Step 4 - 3rd argument is a fixed value, so don't add to signature, only
to the call: fn(int $a, int $b) => foo($a, $b, 42);
The second model is that a partial application starts with a complete
signature, and splices in the fixed values. The steps go more like this:
function foo(int $a, int $b, int $c) { /* whatever */ }
$partial = foo(?, ?, 42);
Step 1 - copy original signature: $partial = fn(int $a, int $b, int $c)
=> foo($a, $b, $c);
Step 2 - 1st argument is a ? so skip over it
Step 3 - 2nd argument is a ? so skip over it
Step 4 - 3rd argument is a fixed value, so splice a value into the
signature at that location: $partial = fn(int $a, int $b, 42) => foo($a,
$b, 42);
Step 5 - fixed values don't actually belong on the left-hand side:
$partial = fn(int $a, int $b) => foo($a, $b, 42);
For simple examples like the above, the two models are essentially
equivalent, but they lead to different expectations for some features;
for instance, trailing arguments and placeholders:
-
In the model where you're building from empty, writing foo(42, ?, ?)
to mean "copy exactly two arguments" feels natural. To say "copy all
arguments", or "copy all remaining arguments", you'd then need a
different syntax, like foo(42, ...) or foo(42, ??). In this model, the
trailing "?" in the current proposal is magic: it changes from meaning
"copy exactly one argument" to "copy all remaining arguments". -
In the model where you're splicing arguments into a full signature,
foo(42, ?, ?) is pointless: the action is to splice in the 42, then
"skip over" two arguments; but nothing happens after skipping over
them, so you could just not mention them. No extra token is needed to
copy all remaining arguments, because that already happened at the
start. The only reason to use a trailing "?" at all is because foo(42)
would just be a normal function call, and adding a redundant "skip over"
marker in foo(42, ?) tells the compiler it's a partial.
Regards,
--
Rowan Tommins
[IMSoP]
It includes an implementation by Joe Watkins that is already about 95% complete. (There's some edge cases he's still sorting out, but all of the typical cases should work already.) Most of the design work comes from Levi Morrison and Paul Crovella. I helped out with the tests, a few edge bits, and general instigator/nudge. :-)
Can I be a little pushy and ask for a subvote on "..." vs "...?"
It probably won't matter, but with ... being used as the language's
universal variadic / unpack, we may use it for similar things in the
years to come, and I think it makes sense to pre-empt a potential
conflict or source of confusion down the line by using "...?".
Just as a variadic function argument is ...$ a variadic partial closure
would be ...?
I do not, at this time, have a firm example of where this might become a
problem, otherwise I would state it. The closest I can think of for now
is that it's entirely possible that auto-capturing multiline functions
may end up using:
$x = function() use (...) { /* */ }
Which could possibly be confusing. Not a definiative example by any
stretch, but but I do get the feeling that, as we can future proof it
for effectively zero cost, we should.
Mark Randall