# Explicit callee defaults

- Thread: https://externals.io/message/124629
- Original archive: https://news-web.php.net/php.internals/124629
- Started by Bilge on 2024-07-26T21:54:05+00:00
- Messages: 11
- RFC: Default expression (Withdrawn) — https://externals.io/rfc/default_expression.md
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #124629 — Bilge — 2024-07-26T21:54:05+00:00

URL: https://externals.io/message/124629#124629

Hi Internals,

New RFC idea just dropped. When writing a function, we can specify 
defaults for its parameters, and when calling a function we can leverage 
those defaults /implicitly/ by not specifying those arguments or by 
"jumping over" some of them using named parameters. However, we cannot 
/explicitly/ use the defaults. But why would we want to?

Sometimes we want to effectively /inherit/ the defaults of a function 
we're essentially just proxying. One way to do that is copy and paste 
the entire method signature, but if the defaults of the proxied method 
change, we're now overriding them with our own, which is not what we 
wanted to do. It is possible, in a roundabout way, to avoid specifying 
the optional parameters by filtering them out (as shown in the next 
example). The final possibility is to use reflection and literally query 
the default value for each optional parameter, which is the most awkward 
and verbose way to inherit defaults.

Let's use a concrete example for clarity.

function query(string $sql, int $limit = PHP_INT_MAX, int $offset = 0);

function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
     query(...array_filter(func_get_args(), fn ($arg) => $arg !== null));
}

In this way we are able to filter out the null arguments to inherit the 
callee defaults, but this code is quite ugly. Moreover, it makes the 
(sometimes invalid) assumption that we're able to use `null` for all the 
optional arguments.

In my new proposal for /explicit /callee defaults, it would be possible 
to use the `default` keyword to expressly use the default value of the 
callee in that argument position. For example, the above implementation 
for myQuery() could be simplified to the following.

function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
     query($sql, $limit ?? default, $offset ?? default);
}

Furthermore, it would also be possible to "jump over" optional 
parameters /without/ using named parameters.

json_decode($json, true, default, JSON_THROW_ON_ERROR);

This proposal is built on the assumption that it is possible to specify 
that PHP should only accept the `default` expression in method and 
function call contexts. For example, it would not be valid to return 
`default` from a function and substitute it that way; my proposal is to 
only permit `default` in literal function calling contexts. My knowledge 
of internals is insufficient (read: non-existent) to know whether or not 
this restriction is possible to implement, but if it is, I think this is 
a good idea. What do you think?

Cheers,
Bilge

---

## #124630 — Christoph M. Becker — 2024-07-26T22:42:47+00:00

URL: https://externals.io/message/124629#124630 · In reply to: #124629

> New RFC idea just dropped. When writing a function, we can specify
> defaults for its parameters, and when calling a function we can leverage
> those defaults /implicitly/ by not specifying those arguments or by
> "jumping over" some of them using named parameters. However, we cannot
> /explicitly/ use the defaults. But why would we want to?
>
> Sometimes we want to effectively /inherit/ the defaults of a function
> we're essentially just proxying. One way to do that is copy and paste
> the entire method signature, but if the defaults of the proxied method
> change, we're now overriding them with our own, which is not what we
> wanted to do. It is possible, in a roundabout way, to avoid specifying
> the optional parameters by filtering them out (as shown in the next
> example). The final possibility is to use reflection and literally query
> the default value for each optional parameter, which is the most awkward
> and verbose way to inherit defaults.
>
> Let's use a concrete example for clarity.
>
> function query(string $sql, int $limit = PHP_INT_MAX, int $offset = 0);
>
> function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
>     query(...array_filter(func_get_args(), fn ($arg) => $arg !== null));
> }
>
> In this way we are able to filter out the null arguments to inherit the
> callee defaults, but this code is quite ugly. Moreover, it makes the
> (sometimes invalid) assumption that we're able to use `null` for all the
> optional arguments.
>
> In my new proposal for /explicit /callee defaults, it would be possible
> to use the `default` keyword to expressly use the default value of the
> callee in that argument position. For example, the above implementation
> for myQuery() could be simplified to the following.
>
> function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
>     query($sql, $limit ?? default, $offset ?? default);
> }
>
> Furthermore, it would also be possible to "jump over" optional
> parameters /without/ using named parameters.
>
> json_decode($json, true, default, JSON_THROW_ON_ERROR);
>
> This proposal is built on the assumption that it is possible to specify
> that PHP should only accept the `default` expression in method and
> function call contexts. For example, it would not be valid to return
> `default` from a function and substitute it that way; my proposal is to
> only permit `default` in literal function calling contexts. My knowledge
> of internals is insufficient (read: non-existent) to know whether or not
> this restriction is possible to implement, but if it is, I think this is
> a good idea. What do you think?

I have only skimmed your suggestion, but it sounds quite similar to
<https://wiki.php.net/rfc/skipparams>.

Cheers,
Christoph

---

## #124632 — Mike Schinkel — 2024-07-27T00:04:25+00:00

URL: https://externals.io/message/124629#124632 · In reply to: #124630

> 
> 
> I have only skimmed your suggestion, but it sounds quite similar to
> <https://wiki.php.net/rfc/skipparams>.

I would really love to hear from some of those who voted "no" ~9 years why they did so, and if they still feel the same.

> 
> When writing a function, we can specify defaults for its parameters, and when calling a function we can leverage those defaults implicitly by not specifying those arguments or by "jumping over" some of them using named parameters. However, we cannot explicitly use the defaults. But why would we want to?
> 
> Sometimes we want to effectively inherit the defaults of a function we're essentially just proxying. One way to do that is copy and paste the entire method signature, but if the defaults of the proxied method change, we're now overriding them with our own, which is not what we wanted to do. It is possible, in a roundabout way, to avoid specifying the optional parameters by filtering them out (as shown in the next example). The final possibility is to use reflection and literally query the default value for each optional parameter, which is the most awkward and verbose way to inherit defaults.
> 
> Let's use a concrete example for clarity.
> 
> function query(string $sql, int $limit = PHP_INT_MAX, int $offset = 0);
> 
> function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
>     query(...array_filter(func_get_args(), fn ($arg) => $arg !== null));
> }
> 

Yes, I run into that issue all the time.  So much so that I adopted an "optional args" pattern which itself has many downsides but in my code it has had fewer downsides than doing it other ways.

So for your example I would write it like this (though I hate having to double-quote the names of the optional args):
function query(string $sql, array $args = []);

function myQuery(string $sql, array $args = []) {
    query($sql, $args);
}

myQuery("SELECT * FROM table;", ["offset" => 100]));

This is very flexible and a pattern that served me well for 10+ years. 

But of course this has none of the type safety nor code-completion benefits of named parameters, which I would sorely like to have.


> In this way we are able to filter out the null arguments to inherit the callee defaults, but this code is quite ugly. Moreover, it makes the (sometimes invalid) assumption that we're able to use `null` for all the optional arguments.
> 
> In my new proposal for explicit callee defaults, it would be possible to use the `default` keyword to expressly use the default value of the callee in that argument position. For example, the above implementation for myQuery() could be simplified to the following.
> 
> function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
>     query($sql, $limit ?? default, $offset ?? default);
> }
> 
> Furthermore, it would also be possible to "jump over" optional parameters without using named parameters.
> 

To me, while nice I feel it is very much like only a quarter step in the right direction.

I do not meaning to highjack your thread; feel free to ignore this if you feel committed to pursing only your stated approach and are not interested in what I would like to see.  But I mention in hopes you and others see value in a more complete approach:

To begin, we can already currently do this (although I do not see many people doing it in PHP; fyi this approach is the norm for many Go programmers):

class QueryArgs {
   public function __construct(
     public ?int $limit = 100,
     public ?int $offset = 0,
   ){}
}

function myQuery(string $sql, QueryArgs $args = new QueryArgs()) {
   query($sql, $args);
}

myQuery("SELECT * FROM table;", new QueryArgs(offset:100));

I can only give my reasons for not doing it myself in PHP and speculate that others may have similar reasons and/or have never even considered it:

1.  Not having an easy and consistent way to declare the "args" class in the same file where the functions that use it are declared which keeps it out-of-sight and makes it harder to keep updated than it needs to be.

2. Having lots of visually annoying boilerplate that obscures a clear use-case.

What would be great IMO would be if PHP could elevate this concept to first class and add syntax and features to streamline and empower its use.  Imagine if we could instead:

1. Define an "args" type which would be based on a class like enum is based on a class:

args QueryArgs {
     public ?int $limit = 100;
     public ?int $offset = 0;
}

2. Declare a function to use those args, using your `default` keyword when they are optional:

function myQuery(string $sql, QueryArgs $args = default) {
   query($sql, $args);
}

3. And finally, allow a streamlined syntax to pass args:

myQuery("SELECT * FROM table;", {offset:100});

(I do not think we can or even should try to tackle the PSR-4 "every symbol in its own file" problem here, so I am going to leave that for other potential RFCs.)

-Mike

---

## #124636 — Bilge — 2024-07-27T07:00:00+00:00

URL: https://externals.io/message/124629#124636 · In reply to: #124630

> I have only skimmed your suggestion, but it sounds quite similar to
> <https://wiki.php.net/rfc/skipparams>.
>
> Cheers,
> Christoph

Hi Christoph,

Yes, it appears to be exactly that.

I suppose that means there is no hope of pursuing this, since the vote 
9.5 years ago was such a total blowout?

Cheers,
Bilge

---

## #124637 — Rob Landers — 2024-07-27T07:19:10+00:00

URL: https://externals.io/message/124629#124637 · In reply to: #124632

> > […]
> 
> I would really love to hear from some of those who voted "no" ~9 years why they did so, and if they still feel the same.
> 
> > […]
> 
> Yes, I run into that issue all the time.  So much so that I adopted an "optional args" pattern which itself has many downsides but in my code it has had fewer downsides than doing it other ways.
> 
> So for your example I would write it like this (though I hate having to double-quote the names of the optional args):
> function query(string $sql, array $args = []);
> 
> function myQuery(string $sql, array $args = []) {
>     query($sql, $args);
> }
> 
> myQuery("SELECT * FROM table;", ["offset" => 100]));
> 
> This is very flexible and a pattern that served me well for 10+ years. 
> 
> But of course this has none of the type safety nor code-completion benefits of named parameters, which I would sorely like to have.
> 
> 
> > […]
> 
> To me, while nice I feel it is very much like only a quarter step in the right direction.
> 
> I do not meaning to highjack your thread; feel free to ignore this if you feel committed to pursing only your stated approach and are not interested in what I would like to see.  But I mention in hopes you and others see value in a more complete approach:
> 
> To begin, we can already currently do this (although I do not see many people doing it in PHP; fyi this approach is the norm for many Go programmers):
> 
> class QueryArgs {
>    public function __construct(
>      public ?int $limit = 100,
>      public ?int $offset = 0,
>    ){}
> }
> 
> function myQuery(string $sql, QueryArgs $args = new QueryArgs()) {
>    query($sql, $args);
> }
> 
> myQuery("SELECT * FROM table;", new QueryArgs(offset:100));
> 
> I can only give my reasons for not doing it myself in PHP and speculate that others may have similar reasons and/or have never even considered it:
> 
> 1.  Not having an easy and consistent way to declare the "args" class in the same file where the functions that use it are declared which keeps it out-of-sight and makes it harder to keep updated than it needs to be.

There’s nothing stopping you from doing that, except your autoloader. If you wanted to have every class ending with Arg load the same class without arg; so QueryArg and Query are usually in the same file (but can be in separate files too), you could do something like this:

<?php
function customArgAutoloader($className) {
    if (substr($className, -3) === 'Arg') {
        $baseClass = substr($className, 0, -3);
        $filePath = __DIR__ . '/' . $baseClass . '.php';

        if (file_exists($filePath)) {
            require_once $filePath;
        } 
    }
}

spl_autoload_register('customArgAutoloader');

(Untested, but you get the idea)



— Rob

---

## #124638 — Mike Schinkel — 2024-07-27T08:42:30+00:00

URL: https://externals.io/message/124629#124638 · In reply to: #124637

> 
> There’s nothing stopping you from doing that, except your autoloader. If you wanted to have every class ending with Arg load the same class without arg; so QueryArg and Query are usually in the same file (but can be in separate files too), you could do something like this:
> 
> <?php
> function customArgAutoloader($className) {
>     if (substr($className, -3) === 'Arg') {
>         $baseClass = substr($className, 0, -3);
>         $filePath = __DIR__ . '/' . $baseClass . '.php';
> 
>         if (file_exists($filePath)) {
>             require_once $filePath;
>         } 
>     }
> }
> 
> spl_autoload_register('customArgAutoloader');
> 
> (Untested, but you get the idea)

Sure, but that is non-standard, so it would be swimming against the current to use it, and no one else would write code that way.

Still, the reason for my comment was to ask we consider a first-class args type which would enable being able to pass parameters like `{foo: 1, bar: 2}` instead of  `new QueryArgs(foo: 1, bar: 2)`, not to discuss autoloaders.

-Mike

---

## #124639 — Christoph M. Becker — 2024-07-27T09:33:13+00:00

URL: https://externals.io/message/124629#124639 · In reply to: #124636

> 
>
> > […]
>
> Yes, it appears to be exactly that.
>
> I suppose that means there is no hope of pursuing this, since the vote
> 9.5 years ago was such a total blowout?

Not necessarily.  It makes probably sense to also check the related
discussions on the mailing list, which do not appear to be listed in the
RFC, but searching for "Skipping optional parameters for functions" on
<https://externals.io/> gives some results.  I havent't looked closely
into these discussions, but apparently one reason why several people
didn't like the proposal was that PHP should have named function
arguments.  If a new RFC can make a good point why `default` still would
make sense despite PHP having named function arguments, the proposal
might be seen in a different light.

Personally, I'm rather neutral on the idea.

Cheers,
Christoph

---

## #124640 — Rob Landers — 2024-07-27T09:41:30+00:00

URL: https://externals.io/message/124629#124640 · In reply to: #124629

> Hi Internals,
> 
> New RFC idea just dropped. When writing a function, we can specify defaults for its parameters, and when calling a function we can leverage those defaults *implicitly* by not specifying those arguments or by "jumping over" some of them using named parameters. However, we cannot *explicitly* use the defaults. But why would we want to?
> 
> Sometimes we want to effectively *inherit* the defaults of a function we're essentially just proxying. One way to do that is copy and paste the entire method signature, but if the defaults of the proxied method change, we're now overriding them with our own, which is not what we wanted to do. It is possible, in a roundabout way, to avoid specifying the optional parameters by filtering them out (as shown in the next example). The final possibility is to use reflection and literally query the default value for each optional parameter, which is the most awkward and verbose way to inherit defaults.
> 
> Let's use a concrete example for clarity.
> 
> function query(string $sql, int $limit = PHP_INT_MAX, int $offset = 0);
> 
> 
> 
> function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
>     query(...array_filter(func_get_args(), fn ($arg) => $arg !== null));
> }
> 
> 
> In this way we are able to filter out the null arguments to inherit the callee defaults, but this code is quite ugly. Moreover, it makes the (sometimes invalid) assumption that we're able to use `null` for all the optional arguments.
> 
> In my new proposal for *explicit *callee defaults, it would be possible to use the `default` keyword to expressly use the default value of the callee in that argument position. For example, the above implementation for myQuery() could be simplified to the following.
> 
> 
> 
> function myQuery(string $sql, ?int $limit = null, ?int $offset = null) {
>     query($sql, $limit ?? default, $offset ?? default);
> }
> 
> 
> Furthermore, it would also be possible to "jump over" optional parameters *without* using named parameters.
> 
> json_decode($json, true, default, JSON_THROW_ON_ERROR);
> 
> This proposal is built on the assumption that it is possible to specify that PHP should only accept the `default` expression in method and function call contexts. For example, it would not be valid to return `default` from a function and substitute it that way; my proposal is to only permit `default` in literal function calling contexts. My knowledge of internals is insufficient (read: non-existent) to know whether or not this restriction is possible to implement, but if it is, I think this is a good idea. What do you think?
> 
> 
> 
> Cheers,
> Bilge
> 
> 

This seems like a case for code generation — and an RFC that provides hooks for code generation would probably be better IMHO.

There are a couple of neat tools out there doing this and hooking into composer, like https://packagist.org/packages/olvlvl/composer-attribute-collector

There are many things that could benefit from this, such as DI containers, scanning for attributes, generating efficient serializers/deserializers, etc. 

— Rob

---

## #124641 — Bilge — 2024-07-27T09:50:09+00:00

URL: https://externals.io/message/124629#124641 · In reply to: #124640

>
> This seems like a case for code generation
I don't understand how this has anything to do with code generation. I 
understand what composer-attribute-collector is doing, and see no 
application for it (or something like it) here. Could you explain a bit 
further?

---

## #124647 — Rob Landers — 2024-07-27T12:52:01+00:00

URL: https://externals.io/message/124629#124647 · In reply to: #124641

> 
> > […]
> I don't understand how this has anything to do with code generation. I understand what composer-attribute-collector is doing, and see no application for it (or something like it) here. Could you explain a bit further?

In your example you show how to write code that does what you want, but also that it is annoying to write by-hand. Thus, generating the code instead of writing it by-hand might be better than a new language feature; and there are many things that would benefit from that.

— Rob

---

## #124648 — Bilge — 2024-07-27T12:57:56+00:00

URL: https://externals.io/message/124629#124648 · In reply to: #124647

> 
> > […]
>
> In your example you show how to write code that does what you want, 
> but also that it is annoying to write by-hand. Thus, generating the 
> code instead of writing it by-hand might be better than a new language 
> feature; and there are many things that would benefit from that.
>
> — Rob

I understand your point is that preprocessors could solve this problem, 
and I agree, preprocessors can solve a lot of problems. However, that 
point seems entirely orthogonal to this discussion.

Cheers,
Bilge
