On 28/11/2023 09:54, Claude Pache wrote:
The big problem with the `callable` type, is that it can be check only at
runtime. For instance:
```php
function foo(callable $x) { }
foo('strlen'); // ok
foo('i_dont_exist'); // throws a TypeError
```
To 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:
```php
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]