Hello, internals! I just want to share some thoughts with you regarding
what could be improved in the first class callable syntax.
It is already possible to create Callable from a static method:
class Foo {
public static function staticmethod() {}
}
$c = Foo::staticmethod(...);
It would be a great pleasure to have the ability to wrap the instance
method the same way. One particular use case for this could be when there's
an array of items and it is necessary to call some getter-method on all of
them.
class Verification
{
public function __construct(private string $id) {}
public function getId(): string
{
return $this->id;
}
}
$items = [new Verification('1-2-3-4')];
array_map(Verification::getId(...), $items);
// previous line is an equivalent of this
array_map(static fn (Verification $v) => $v->getId(), $items);
Currently this syntax fails in run-time rather than compile-time.
PHP Fatal error: Uncaught Error: Non-static method Verification::getId()
cannot be called statically in /opt/scratches/scratch_439.php:21
Please, let me know what you think about it.
Best regards, Yeven
Hello, internals! I just want to share some thoughts with you regarding
what could be improved in the first class callable syntax.It is already possible to create Callable from a static method:
class Foo { public static function staticmethod() {} } $c = Foo::staticmethod(...);
It would be a great pleasure to have the ability to wrap the instance
method the same way. One particular use case for this could be when there's
an array of items and it is necessary to call some getter-method on all of
them.class Verification { public function __construct(private string $id) {} public function getId(): string { return $this->id; } } $items = [new Verification('1-2-3-4')]; array_map(Verification::getId(...), $items); // previous line is an equivalent of this array_map(static fn (Verification $v) => $v->getId(), $items);
Currently this syntax fails in run-time rather than compile-time.
PHP Fatal error: Uncaught Error: Non-static method Verification::getId() cannot be called statically in /opt/scratches/scratch_439.php:21
Please, let me know what you think about it.
Best regards, Yeven
Hello,
This has been brought up a couple of times, but I can't seem to find
it. I don't think something like this is possible with the current
implementation of first-class-callables (it would need a major
refactor). Currently adding the callable bit like func(...)
is
roughly the same as just writing a string 'func'
and passing that
around (IIRC, that's literally what it gets turned to after parsing).
The only difference over using a string, is that there is some
validation to check that it is actually a function you can call.
Basically, there's currently no way to do something like what you
suggest in the engine, which makes it quite a big feature. That being
said, we shouldn't be afraid of doing hard things, but it would be a
lot of work for saving just a few characters of typing every now and
then.
Perhaps it would be easier after partial application could be
implemented (latest discussion here:
https://externals.io/message/119678#119678), or maybe someone else has
some better ideas or thoughts.
Cheers,
Rob Landers
Utrecht, Netherlands
This has been brought up a couple of times, but I can't seem to find
it.
https://externals.io/message/119392
https://externals.io/message/120011
I don't think something like this is possible with the current
implementation of first-class-callables (it would need a major
refactor).
It's not currently possible, but it wouldn't need a huge refactor...we
could just do what Java does:
https://www.baeldung.com/java-method-references
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
// If we copied the Java way of doing it, this:
// $fn = Verification::getId(...);
// $fn would be equivalent to:
$fn = fn (Verification $v) => $v->getId();
And if the method of the class has parameters, those would be after
the first instance parameter:
class Foo {
function bar(int $x) {}
}
$fn = Foo::bar(...);
// Would be equivalent to:
$fn = fn (Foo $f, int $x) => $f->bar($x);
I can't see a technical reason not to do it like this, but some people
seem to be having negative gut reactions to it, and it's hard to
persuade someone about aesthetics.
Notes here: https://phpopendocs.com/rfc_codex/class_method_callable
cheers
Dan
Ack
I can't see a technical reason not to do it like this, but some people
seem to be having negative gut reactions to it, and it's hard to
persuade someone about aesthetics.Notes here: https://phpopendocs.com/rfc_codex/class_method_callable
cheers
Dan
Ack
I'd be down for implementing this in a heartbeat if we can agree on a
syntax. I've run into this issue several times and have thought about
it, just never really done anything about it.
Hmm, yeah, looking at the implementation again, it may not be that
big of a change. If I'm understanding correctly, it might need a new
op-code (but could be done using the existing ZEND_CALLABLE_CONVERT),
but otherwise should be rather straightforward. The bigger changes are
going to be in the Callable implementation. As mentioned, this 'view
of the world' isn't really supported by the engine. You can't perform
this operation (calling a method on an interchangeable object) without
another closure (to my knowledge) to do so.
Perhaps we could sidestep the syntax issue by taking some inspiration
from Rowan(?) and just implement this as a method on \Closure or
\Callable, maybe something like this:
$a = Closure::fromCallable(static fn () => $this->getId());
$b = Closure::bindMany($items); // executes bind
on each item in the
array, returning an array of closures
foreach($b as $callback) {
$callback();
}
or something like that. My biggest thing is that there's no real way
to save much on typing this out. Unless you are doing something fairly
exotic, you only save a few characters. That's why I said its a lot of
work for not much benefit. I could be totally wrong though because I,
personally, do run into wanting this feature several times a year.
It would be a great pleasure to have the ability to wrap the instance
method the same way.
The first-class callable syntax works just fine with instance methods, but you need to use it on a particular instance: $someInstance->someMethod(...)
Unfortunately, this is no use for your example, essentially because PHP (unlike, say, Python) doesn't consider $this to be part of the parameters to a method: Verification::getId(...) would mean fn($item) => Verification::getId($item) but you can't call an instance method that way.
What you want is fn($item) => $item->getId(). A syntax like ...->getId() wouldn't be much use - it wouldn't be able to check for the method's existence in advance (the "call to non-static" error in your example happens as soon as you try to create the closure https://3v4l.org/G8va8), or copy over any type information, because there's no way to deduce what class will be used later.
So maybe it would need some other indicator, like Verification::getId(instance ...) to indicate that you want a closure with an extra parameter to be used as the instance. Maybe someone has a less ugly suggestion?
Regards,
--
Rowan Tommins
[IMSoP]