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]

Reply via email to