Re: Callable arguments cannot have default value
| From: | Rowan Tommins | Date: | Tue, 28 Nov 2023 12:49:30 +0000 |
| Subject: | Re: Callable arguments cannot have default value | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-121821@lists.php.net to get a copy of this message | ||
On 28/11/2023 09:54, Claude Pache wrote:
The big problem with theTo expand on this example, and address the original question more explicitly, consider if we allowed this: function foo(callable $x = 'maybe_exists') { } To decide whether that's a valid definition, the compiler needs to know whether 'maybe_exists' can be resolved to the name of a global function; but it might be defined in a different file, which hasn't been included yet (or, more generally, which isn't being compiled right now). To allow the default, the engine would need to defer the validity check until the function is actually executed. This is how "new in initializers" works [https://wiki.php.net/rfc/new_in_initializers] and we can actually use that feature to implement a default for callable parameters:callabletype, is that it can be check only at runtime. For instance:function foo(callable $x) { } foo('strlen'); // ok foo('i_dont_exist'); // throws a TypeError
class WrappedCallable {
// Note: can't declare callable as the property type, but can as an explicit constructor parameter
private $callable;
public function __construct(callable $callable) {
$this->callable = $callable;
}
public function __invoke(...$args) { return ($this->callable)(...$args); }
}
function test(callable $f = new WrappedCallable('strlen')) {
echo $f('hello');
}
test();
Using this wrapper, we can pass in any value which is itself valid in an initializer, including callables specified as 'funcname' or ['class', 'staticmethod'].
The trick is that we're not actually evaluating that value as a callable until we invoke test(), at which point the constructor of WrappedCallable performs the assertion that it's actually callable. So this compiles:
function test(callable $f = new WrappedCallable('i_dont_exist')) {
echo $f('hello');
}
But will then error at run-time, *unless* a global function called i_dont_exist has been defined before that call.
It seems like it would be feasible for the engine to do something similar natively, creating an equivalent of WrappedCallable('i_dont_exist') using the first-class callable syntax:
function test(callable $f = i_dont_exist(...)) {
echo $f('hello');
}
Regards,
--
Rowan Tommins
[IMSoP]