[PHP-DEV] [RFC] Default expression

Hi gang,

New RFC just dropped: PHP: rfc:default_expression. I think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I've been cooking for a little while. Considering I don't know C or PHP internals, one might think implementing this feature would be prohibitively difficult, but considering the amount of help and guidance I received from Ilija, Bob and others, it would be truer to say it would have been more difficult to fail! Huge thanks to them.

Cheers,
Bilge

On Sat, Aug 24, 2024, 11:50 AM Bilge <bilge@scriptfusion.com> wrote:

Hi gang,

New RFC just dropped: https://wiki.php.net/rfc/default_expression. I
think some of you might enjoy this one. Hit me with any feedback.

This is a feature I’ve wanted for a very long time! The RFC is very straight forward, and the appendix does a great job of enumerating the possible expressions.

Nice work all around!

This one already comes complete with working implementation that I’ve
been cooking for a little while. Considering I don’t know C or PHP
internals, one might think implementing this feature would be
prohibitively difficult, but considering the amount of help and guidance
I received from Ilija, Bob and others, it would be truer to say it would
have been more difficult to fail! Huge thanks to them.

Cheers,
Bilge

On 24/08/2024 18:01, Matthew Weier O'Phinney wrote:

This is a feature I've wanted for a very long time! The RFC is very straight forward, and the appendix does a great job of enumerating the possible expressions.

Nice work all around!

Thanks, Matt! Glad you like this one (and the last one!)

Hopefully we can land it this time :slightly_smiling_face:

Cheers,
Bilge

On Sat, Aug 24, 2024, at 18:49, Bilge wrote:

Hi gang,

New RFC just dropped: https://wiki.php.net/rfc/default_expression. I

think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I’ve

been cooking for a little while. Considering I don’t know C or PHP

internals, one might think implementing this feature would be

prohibitively difficult, but considering the amount of help and guidance

I received from Ilija, Bob and others, it would be truer to say it would

have been more difficult to fail! Huge thanks to them.

Cheers,

Bilge

This is pretty awesome! I see this as some syntax sugar, to be honest:

as soon as two or more nullable arguments are involved

I’m not sure what you mean here. I use this method all the time :slight_smile: much to the chagrin of some of my coworkers.

function stuff($foo = ‘bar’, $baz = ‘world’);

stuff(…[ …($foo ? [‘foo’ => $foo] : ), …($baz ? [‘baz’ => $baz] : )]);

Having this would be a lot less verbose.

— Rob

On 24/08/2024 22:14, Rob Landers wrote:

On Sat, Aug 24, 2024, at 18:49, Bilge wrote:

as soon as two or more nullable arguments are involved

I'm not sure what you mean here. I use this method all the time :slight_smile: much to the chagrin of some of my coworkers.

function stuff($foo = 'bar', $baz = 'world');

stuff(...[ ...($foo ? ['foo' => $foo] : ), ...($baz ? ['baz' => $baz] : )]);

You're right; splat with keys does work. This is something my RFC currently glosses over and should probably be rectified!

Cheers,
Bilge

On Aug 24, 2024 at 5:16 PM, <Rob Landers> wrote:

I’m not sure what you mean here. I use this method all the time :slight_smile: much to the chagrin of some of my coworkers.

function stuff($foo = ‘bar’, $baz = ‘world’);

stuff(…[ …($foo ? [‘foo’ => $foo] : ), …($baz ? [‘baz’ => $baz] : )]);

And you are one who complains about gotos! :astonished:

-Mike

On Aug 24, 2024, at 11:49, Bilge <bilge@scriptfusion.com> wrote:

Hi gang,

New RFC just dropped: PHP: rfc:default_expression. I think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I've been cooking for a little while. Considering I don't know C or PHP internals, one might think implementing this feature would be prohibitively difficult, but considering the amount of help and guidance I received from Ilija, Bob and others, it would be truer to say it would have been more difficult to fail! Huge thanks to them.

Cheers,
Bilge

Great RFC, Bilge! I was already on-board after the introduction, but if I had any doubts, the examples in the appendix sold me.

Cheers,
Ben

On 25/08/2024 04:06, Ben Ramsey wrote:

Great RFC, Bilge! I was already on-board after the introduction, but if I had any doubts, the examples in the appendix sold me.

Cheers,
Ben

Thanks, Ben. That means a lot to me :slight_smile:

Cheers,
Bilge

On Sun, Aug 25, 2024, at 04:41, Mike Schinkel wrote:

On Aug 24, 2024 at 5:16 PM, <Rob Landers> wrote:

I’m not sure what you mean here. I use this method all the time :slight_smile: much to the chagrin of some of my coworkers.

function stuff($foo = ‘bar’, $baz = ‘world’);

stuff(…[ …($foo ? [‘foo’ => $foo] : ), …($baz ? [‘baz’ => $baz] : )]);

And you are one who complains about gotos! :astonished:

-Mike

Haha, there is a difference between production/professional code and internal tools. Internal tools are a place to experiment and have a little fun, IMHO.

— Rob

Hi Bilge, I like the idea, but see some potential for issues with ambiguity, which I don’t see mentioned in the RFC as “solved”. Example 1: php function foo($paramA, $default = false) {} foo( default: default ); // <= Will this be handled correctly ? Example 2: php callme( match($a) { 10 => $a * 10, 20 => $a * 20, default => $a * default, // <= Based on a test in the PR this should work. Could you confirm ? } ); Example 3: php switch($a) { case 'foo': return callMe($a, default); // I presume this shouldn't be a problem, but might still be good to have a test for this ? default: return callMe(10, default); // I presume this shouldn't be a problem, but might still be good to have a test for this ? } On that note, might it be an idea to introduce a separate token for the default keyword when used as a default expression in a function call to reduce ambiguity ? Smile, Juliette

···

(resending as I accidentally originally send a private reply instead of sending the below to the list)

On 24-8-2024 18:49, Bilge wrote:

Hi gang,

New RFC just dropped: https://wiki.php.net/rfc/default_expression. I think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I’ve been cooking for a little while. Considering I don’t know C or PHP internals, one might think implementing this feature would be prohibitively difficult, but considering the amount of help and guidance I received from Ilija, Bob and others, it would be truer to say it would have been more difficult to fail! Huge thanks to them.

Cheers,
Bilge

On 25/08/2024 10:49, Juliette Reinders Folmer wrote:

(resending as I accidentally originally send a private reply instead of sending the below to the list)

On 24-8-2024 18:49, Bilge wrote:

Hi gang,

New RFC just dropped: PHP: rfc:default_expression. I think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I've been cooking for a little while. Considering I don't know C or PHP internals, one might think implementing this feature would be prohibitively difficult, but considering the amount of help and guidance I received from Ilija, Bob and others, it would be truer to say it would have been more difficult to fail! Huge thanks to them.

Cheers,
Bilge

Hi Bilge,

Hi :slight_smile:

I like the idea, but see some potential for issues with ambiguity, which I don't see mentioned in the RFC as "solved".

Example 1:

function foo($paramA, $default = false) {}
foo( default: default ); // <= Will this be handled correctly ?

No, but not because of my RFC, but because $paramA is a required parameter that was not specified. Assuming that was just a typo, the following works as expected:

function foo($paramA = 1, $default = false) {
var_dump($default);
}
foo(default: default); // bool(false)

Example 2:

callme(
    match($a) {
        10 => $a * 10,
        20 => $a * 20,
        default => $a * default, // <= Based on a test in the PR this should work. Could you confirm ?
    }
);

Yes.

Example 3:

switch($a) {
    case 'foo':
        return callMe($a, default); // I presume this shouldn't be a problem, but might still be good to have a test for this ?
    default:
        return callMe(10, default); // I presume this shouldn't be a problem, but might still be good to have a test for this ?
}

Yes.

On that note, might it be an idea to introduce a separate token for the `default` keyword when used as a default expression in a function call to reduce ambiguity ?

Considering the Bison grammar compiles, I believe there can be no ambiguity. I specifically picked `default` because I think it is the most intuitive keyword to use for this, and it's conveniently already a reserved word.

Cheers,
Bilge

On Sun, Aug 25, 2024, at 12:01, Bilge wrote:

On 25/08/2024 10:49, Juliette Reinders Folmer wrote:

(resending as I accidentally originally send a private reply instead

of sending the below to the list)

On 24-8-2024 18:49, Bilge wrote:

Hi gang,

New RFC just dropped: https://wiki.php.net/rfc/default_expression. I

think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I’ve

been cooking for a little while. Considering I don’t know C or PHP

internals, one might think implementing this feature would be

prohibitively difficult, but considering the amount of help and

guidance I received from Ilija, Bob and others, it would be truer to

say it would have been more difficult to fail! Huge thanks to them.

Cheers,

Bilge

Hi Bilge,

Hi :slight_smile:

I like the idea, but see some potential for issues with ambiguity,

which I don’t see mentioned in the RFC as “solved”.

Example 1:

function foo($paramA, $default = false) {}

foo( default: default ); // <= Will this be handled correctly ?

No, but not because of my RFC, but because $paramA is a required

parameter that was not specified. Assuming that was just a typo, the

following works as expected:

function foo($paramA = 1, $default = false) {

var_dump($default);

}

foo(default: default); // bool(false)

Example 2:

callme(

match($a) {

10 => $a * 10,

20 => $a * 20,

default => $a * default, // <= Based on a test in the PR this

should work. Could you confirm ?

}

);

Yes.

Example 3:

switch($a) {

case ‘foo’:

return callMe($a, default); // I presume this shouldn’t be a

problem, but might still be good to have a test for this ?

default:

return callMe(10, default); // I presume this shouldn’t be a

problem, but might still be good to have a test for this ?

}

Yes.

On that note, might it be an idea to introduce a separate token for

the default keyword when used as a default expression in a function

call to reduce ambiguity ?

Considering the Bison grammar compiles, I believe there can be no

ambiguity. I specifically picked default because I think it is the

most intuitive keyword to use for this, and it’s conveniently already a

reserved word.

Other tools parse the tokens directly (for example, I have a tool to take php classes and convert them to graphql specifications. It parses the tokens emitted the tokenization extension), and having “default” tokens in unexpected places presents an ambiguity and BC break for those tools. By having a token (DEFAULT_PARAM_VALUE) or something to disambiguate might be better. I had assumed it was a separate token when I first read it, so this is a good point.

— Rob

On Sat, Aug 24, 2024, at 11:49 AM, Bilge wrote:

Hi gang,

New RFC just dropped: PHP: rfc:default_expression. I
think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I've
been cooking for a little while. Considering I don't know C or PHP
internals, one might think implementing this feature would be
prohibitively difficult, but considering the amount of help and guidance
I received from Ilija, Bob and others, it would be truer to say it would
have been more difficult to fail! Huge thanks to them.

Cheers,
Bilge

I am still not fully sold on this, but I like it a lot better than the previous attempt at a default keyword. It's good that you mention named arguments, as those do replace like 95% of the use cases for "put default here" in potential function calls, and the ones it doesn't, you call out explicitly as the justification for this RFC.

The approach here seems reasonable overall. The mental model I have from the RFC is "yoink the default value out of the function, drop it into this expression embedded in the function call, and let the chips fall where they may." Is that about accurate?

My main holdup is the need. I... can't recall ever having a situation where this is something I needed. Some of the examples show valid use cases (eg, the "default plus this binary flag" example), but again, I've never actually run into that myself in practice.

My other concern is the list of supported expression types. I understand how the implementation would naturally make all of those syntactically valid, but it seems many of them, if not most, are semantically nonsensical. Eg, `default > 1` would take a presumably numeric default value and output a boolean, which should really never be type compatible with the function being called. (A param type of int|bool is a code smell at best, and a fatal waiting to happen at worst.) In practice, I think a majority of those expressions would be logically nonsensical, so I wonder if it would be better to only allow a few reasonable ones and block the others, to keep people from thinking nonsensical code would do something useful.

--Larry Garfield

On Aug 24 2024, at 12:49 pm, Bilge bilge@scriptfusion.com wrote:

Hi gang,

New RFC just dropped: PHP: rfc:default_expression. I
think some of you might enjoy this one. Hit me with any feedback.

Seems like you are missing an option for your theme example, which would be to simply extend the Config class?

While I can see the value in the concept of default , I think it’s a mistake to allow default to be used as a generic operand as shown in the RFC appendix. It seems to me that the whole point of something like default is to not have to worry about what the upstream API wanted for that value, but the second you start allowing operations where default is an operand you’ve reintroduced the problem you were trying to avoid if the upstream API were to change the type of what default ultimately resolves to. Worse actually because now I have no idea what default is when I read code without having to dig up the upstream API.

Other thoughts here are what happens when default resolves to an object or enumeration or something complex? Your original example had CuteTheme , so can you call a method of default ?? I could entirely see someone doing something like this for example:

enum Foo:string {
// cases

public function buildSomeValidBasedOnCase(): int { // … }
}

F(MyClass::makeBasedOnValue(default->buildSomeValidBasedOnCase()))

IMO most operators listed in the appendix should be disallowed. I can see the value of default | JSON_PRETTY_PRINT, but I am pretty strongly opposed to the idea of introducing a “conditional based on the default value of an upstream API call” concept of default >=1 .

On Sun, Aug 25, 2024, at 15:35, Larry Garfield wrote:

On Sat, Aug 24, 2024, at 11:49 AM, Bilge wrote:

Hi gang,

New RFC just dropped: https://wiki.php.net/rfc/default_expression. I

think some of you might enjoy this one. Hit me with any feedback.

This one already comes complete with working implementation that I’ve

been cooking for a little while. Considering I don’t know C or PHP

internals, one might think implementing this feature would be

prohibitively difficult, but considering the amount of help and guidance

I received from Ilija, Bob and others, it would be truer to say it would

have been more difficult to fail! Huge thanks to them.

Cheers,

Bilge

I am still not fully sold on this, but I like it a lot better than the previous attempt at a default keyword. It’s good that you mention named arguments, as those do replace like 95% of the use cases for “put default here” in potential function calls, and the ones it doesn’t, you call out explicitly as the justification for this RFC.

The approach here seems reasonable overall. The mental model I have from the RFC is “yoink the default value out of the function, drop it into this expression embedded in the function call, and let the chips fall where they may.” Is that about accurate?

My main holdup is the need. I… can’t recall ever having a situation where this is something I needed. Some of the examples show valid use cases (eg, the “default plus this binary flag” example), but again, I’ve never actually run into that myself in practice.

Potentially the most useful place would be in attributes. Take crell\serde (:p) for instance:

#[SequenceField(implodeOn: default . ’ ', joinOn: ’ ’ . default . ’ ')]

Where you may just want it to be a little more readable, but aren’t interested in the default implosion. In attributes, it has to be a static expression and I think this passes that test? At least that is one place I would find most useful.

Then there are things like the example I gave before, where you need to call some library code as library code and pass through the intentions. It also gets us one step closer to something like these shenanigans:

function configureSerializer(Serde $serializer = new SerdeCommon(formatters: default as $formatters));

Where we can call configureSerializer(formatters: new JsonStreamFormatter()).

Some pretty interesting stuff.

My other concern is the list of supported expression types. I understand how the implementation would naturally make all of those syntactically valid, but it seems many of them, if not most, are semantically nonsensical. Eg, default > 1 would take a presumably numeric default value and output a boolean, which should really never be type compatible with the function being called. (A param type of int|bool is a code smell at best, and a fatal waiting to happen at worst.) In practice, I think a majority of those expressions would be logically nonsensical, so I wonder if it would be better to only allow a few reasonable ones and block the others, to keep people from thinking nonsensical code would do something useful.

I’m reasonably certain you can write nonsensical PHP without this feature. I don’t think we should be the nanny of developers.

— Rob

On Sun, Aug 25, 2024, 9:06 AM John Coggeshall <john@coggeshall.org> wrote:

On Aug 24 2024, at 12:49 pm, Bilge <bilge@scriptfusion.com> wrote:

Hi gang,

New RFC just dropped: https://wiki.php.net/rfc/default_expression. I
think some of you might enjoy this one. Hit me with any feedback.

Seems like you are missing an option for your theme example, which would be to simply extend the Config class?

While I can see the value in the concept of default , I think it’s a mistake to allow default to be used as a generic operand as shown in the RFC appendix. It seems to me that the whole point of something like default is to not have to worry about what the upstream API wanted for that value, but the second you start allowing operations where default is an operand you’ve reintroduced the problem you were trying to avoid if the upstream API were to change the type of what default ultimately resolves to. Worse actually because now I have no idea what default is when I read code without having to dig up the upstream API.

If the underlying API changes the argument type, consumers will have an issue regardless. For those cases where the expression is simply default, you’d actually be protected from the API change, which is a net benefit already.

This also protects the user from changes in the argument names.

Other thoughts here are what happens when default resolves to an object or enumeration or something complex? Your original example had CuteTheme , so can you call a method of default ?? I could entirely see someone doing something like this for example:

enum Foo:string {
// cases

public function buildSomeValidBasedOnCase(): int { // … }
}

F(MyClass::makeBasedOnValue(default->buildSomeValidBasedOnCase()))

IMO most operators listed in the appendix should be disallowed. I can see the value of default | JSON_PRETTY_PRINT, but I am pretty strongly opposed to the idea of introducing a “conditional based on the default value of an upstream API call” concept of default >=1 .

If the underlying API changes the argument type, consumers will have an issue regardless. For those cases where the expression is simply default, you’d actually be protected from the API change, which is a net benefit already.

This also protects the user from changes in the argument names.

As I said, I don’t have a particular problem with default as a keyword to express “whatever the default value might be in the function declaration”, but I do have some real concerns about its use as an operand in an expression. The RFC provides for a single valid use case of operators (i.e. things like default | JSON_PRETTY_PRINT ), yet calls for a huge array of valid operations, many of which the RFC itself notes don’t make much / any sense. I’d personally like to see this RFC dramatically reduce the scope of operations supported with default as an operand initially (e.g. perhaps only bitwise ops), and revisit additional operations as needed down the road. IMO there is a very small subset of all PHP operators that make any sense at all in this context, and even fewer that I think are a good idea to allow even if they might make some sort of sense.

On Sun, Aug 25, 2024, at 16:58, John Coggeshall wrote:

If the underlying API changes the argument type, consumers will have an issue regardless. For those cases where the expression is simply default, you’d actually be protected from the API change, which is a net benefit already.

This also protects the user from changes in the argument names.

As I said, I don’t have a particular problem with default as a keyword to express “whatever the default value might be in the function declaration”, but I do have some real concerns about its use as an operand in an expression. The RFC provides for a single valid use case of operators (i.e. things like default | JSON_PRETTY_PRINT ), yet calls for a huge array of valid operations, many of which the RFC itself notes don’t make much / any sense. I’d personally like to see this RFC dramatically reduce the scope of operations supported with default as an operand initially (e.g. perhaps only bitwise ops), and revisit additional operations as needed down the road. IMO there is a very small subset of all PHP operators that make any sense at all in this context, and even fewer that I think are a good idea to allow even if they might make some sort of sense.

Which operants don’t make sense?

— Rob

On Aug 25 2024, at 11:11 am, Rob Landers rob@bottled.codes wrote:

Which operants don’t make sense?

Well certainly all of the ones toward the end of the appendix in the RFC the RFC itself notes are non-sensical. Personally, I’m not sold on the idea default should be an operand in an expression at all though.

I do see the value of bitwise operators / expressions as highlighted in the RFC. There might be other cases, but I’m wary of them – I don’t think giving developers the ability to write logic and expressions against hard-coded default values in upstream APIs has a lot of merit in most cases. I can tell you I doubt I would ever support the idea of calling methods of default , which I’m still unclear if this RFC is proposing.

I would suggest a compromise where the RFC be refocused to those expressions and operators that explicitly make sense, and leave the rest of them out for now. To me, that basically means “default with bitwise expression support” based on what I’ve seen so far.

John

Yes, as it happens. That is the approach we took, because the alternative would have been changing how values are sent to functions, which would have required a lot more changes to the engine with no clear benefit. Internally it literally calls the reflection API, but a low-level call, that elides the class instantiation and unnecessary hoops of the public interface that would just slow it down. That’s fine. Not everyone will have such a need, and of those that do, I’m willing to bet it will be rare or uncommon at best. But for those times it is needed, the frequency by which it is needed in no way diminishes its usefulness.

···

On 25/08/2024 14:35, Larry Garfield wrote:

The approach here seems reasonable overall.  The mental model I have from the RFC is "yoink the default value out of the function, drop it into this expression embedded in the function call, and let the chips fall where they may."  Is that about accurate?
My main holdup is the need.  I... can't recall ever having a situation where this is something I needed.  Some of the examples show valid use cases (eg, the "default plus this binary flag" example), but again, I've never actually run into that myself in practice.

I rarely use goto but that doesn’t mean we shouldn’t have the feature.

My other concern is the list of supported expression types.  I understand how the implementation would naturally make all of those syntactically valid, but it seems many of them, if not most, are semantically nonsensical.  Eg, `default > 1` would take a presumably numeric default value and output a boolean, which should really never be type compatible with the function being called.  (A param type of int|bool is a code smell at best, and a fatal waiting to happen at worst.)  In practice, I think a majority of those expressions would be logically nonsensical, so I wonder if it would be better to only allow a few reasonable ones and block the others, to keep people from thinking nonsensical code would do something useful.

Since you’re not the only one raising this, I will address it, but just to say there is no good reason, in my mind, to ever prohibit the expressiveness. To quote Rob

I’m reasonably certain you can write nonsensical PHP without this feature. I don’t think we should be the nanny of developers.

I fully agree with that sentiment. It seems to be biting me that I went to the trouble of listing out every permutation of what expression means where perhaps this criticism would not have been levied at all had I chosen not to do so. Why does that matter? Because PHP already allows you to do many more ridiculous things, they’re just not routinely presented to you so they’re not part of your mind map. The end-user documentation will also not mention the nonsense cases, so the average developer will not think of them. You can write, include(1 + 1);, because include() accepts an expression. You will get: “Failed opening ‘2’ for inclusion”. Should we restrict that? No, because that’s just how expressions work in any context where they’re allowed. Special-casing the T_DEFAULT grammar would not only bloat the grammar rules but also increase the chance that new expression grammars introduced in future, which could conveniently interoperate with default, would be unintentionally excluded by omission.

Cheers,
Bilge