Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On 14 April 2023 05:35:39 BST, "Michał Marcin Brzuchalski" wrote: >Have you thought about not populating property by default but instead: >* adding "use" language construct as default to all methods? >* adding ability to assign variable values from "use" to property if needed? > >Like desugar to >$c = class ($a, $b) use ($c) { >private $c = $c; // optional, not required, no conflicts >public function __construct(private int $a, private int $b) use ($c) { >$this->c = $c % 2 ? 3 : 5; >} >public function something() use ($c) { >return $c; >} >} This is closer to what I originally proposed, but with different syntax. The challenge, as was pointed out to me, is that the anonymous class is compiled as a reusable class definition on first use, and a new instance construction on each execution, so you have to think about what happens at each stage. In this example, you've got to first compile something where $c is an open parameter of some sort, and make it available to all the contexts where it might be used; then, when you create the instance, supply a value for $c, and attach it to the instance somewhere; and finally, when something() is called, look up that attached value and make it available to the method. In other words, the use() clause on the class ends up acting like a constructor parameter, and the references in methods end up acting like property references. It's a lot of extra complication to duplicate things the language already has. Having played with it a bit while implementing, I also like the conciseness of objects with no explicit body at all: $who = new class use ($firstName as string, $lastName as string) {}; echo $who->firstName; Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
Em qui., 13 de abr. de 2023 às 09:40, Nicolas Grekas < nicolas.grekas+...@gmail.com> escreveu: > > I created this draft RFC to help move things forward: > > https://wiki.php.net/rfc/syntax-to-capture-variables-when-declaring-anonymous-classes > > Please let me know your early thoughts and I'd be happy to move it to > "under discussion". > I'd also need someone for the implementation as I doubt I'll be able to > write it myself in a reasonable time! > > Cheers, > Nicolas > Hi Nicolas, I believe the $b parameter is missing from the first code block on the Transformation Rules's section. Good luck with the RFC, I hope it passes. -- Daniel Vargas Muccillo
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
Hi Nicolas, czw., 13 kwi 2023 o 14:40 Nicolas Grekas napisał(a): > Hi Rowan, hi all! > > Le ven. 17 mars 2023 à 15:51, Larry Garfield a > écrit : > > > On Thu, Mar 16, 2023, at 6:05 PM, Rowan Tommins wrote: > > > On 16/03/2023 22:14, Larry Garfield wrote: > > >> Wouldn't the functionality described boil down to essentially just > > materializing into a few extra lines in the constructor? At least to my > > ignorant non-engine brain it seems straightforward to have this: > > >> > > >> $a = 1; > > >> $b = 2; > > >> $c = 3; > > >> > > >> $o = new class ($a, $b) use ($c) { > > >>public function __construct(private int $a, private int $b) {} > > >>public function something() {} > > >> } > > >> > > >> Desugar to this: > > >> > > >> $c = class ($a, $b) use ($c) { > > >>private $c; > > >>public function __construct(private int $a, private int $b) { > > >> $this->c = 3; // By value only, so this should be fine? > > >>} > > >>public function something() {} > > >> } > Have you thought about not populating property by default but instead: * adding "use" language construct as default to all methods? * adding ability to assign variable values from "use" to property if needed? Like desugar to $c = class ($a, $b) use ($c) { private $c = $c; // optional, not required, no conflicts public function __construct(private int $a, private int $b) use ($c) { $this->c = $c % 2 ? 3 : 5; } public function something() use ($c) { return $c; } } Or there is something so wrong with this thinking I cannot see yet. Cheers, Michał Marcin Brzuchalski
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Thu, 13 Apr 2023 at 13:40, Nicolas Grekas wrote: > > I created this draft RFC to help move things forward: > > https://wiki.php.net/rfc/syntax-to-capture-variables-when-declaring-anonymous-classes > > Please let me know your early thoughts and I'd be happy to move it to > "under discussion". > I'd also need someone for the implementation as I doubt I'll be able to > write it myself in a reasonable time! > Hi Nicolas! Thanks, it's really encouraging to have some interest in the feature. The good news is that I have an implementation of this nearly ready (it passes all my tests, but the code's a bit messy). I was hoping to polish it over the Easter weekend and draft a PR, but wasn't feeling very well. It has full support for visibility, types, references, and property renaming, but no merging of parameter lists or automatic call to the parent constructor. I think with clean errors that's a good first feature set, and if someone comes up with an implementation for manipulating existing constructors, that can be proposed later. So, "watch this space", as the saying goes :) Regards, -- Rowan Tommins [IMSoP]
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
Hi Rowan, hi all! Le ven. 17 mars 2023 à 15:51, Larry Garfield a écrit : > On Thu, Mar 16, 2023, at 6:05 PM, Rowan Tommins wrote: > > On 16/03/2023 22:14, Larry Garfield wrote: > >> Wouldn't the functionality described boil down to essentially just > materializing into a few extra lines in the constructor? At least to my > ignorant non-engine brain it seems straightforward to have this: > >> > >> $a = 1; > >> $b = 2; > >> $c = 3; > >> > >> $o = new class ($a, $b) use ($c) { > >>public function __construct(private int $a, private int $b) {} > >>public function something() {} > >> } > >> > >> Desugar to this: > >> > >> $c = class ($a, $b) use ($c) { > >>private $c; > >>public function __construct(private int $a, private int $b) { > >> $this->c = 3; // By value only, so this should be fine? > >>} > >>public function something() {} > >> } > > > > > > Not quite - as Ilija pointed out, the class definition gets compiled > > once, but the capture needs to happen for every instance, with > > (potentially) different values of $c. In other words, $c needs to be > > injected as a constructor argument, not a constant in the class > definition. > > > > That's still fine, in principle - you can compile to this: > > > > $o = class ($a, $b, $c) { > >public function __construct(private int $a, private int $b, private > $c) { > >} > >public function something() {} > > } > > > > Or once constructor promotion is de-sugared as well, this: > > > > $o = class ($a, $b, $c) { > >private int $a; > >private int $b; > >private $c; > > > >public function __construct($a, $b, $c) { > > $this->a = $a; // from constructor promotion > > $this->b = $b; // from constructor promotion > > $this->c = $c; // from use() statement > > // other lines from body of constructor go here > >} > >public function something() {} > > } > > > > > > It just introduces a lot of extra cases to handle: > > > > * If there's no constructor, create one > > * If there is a constructor with other arguments, merge the argument > > lists; since there will then be an explicit argument list to "new > > class()", merge those lists as well > > * Maybe different handling if those other arguments are already using > > constructor promotion, as in this example > > * If there are existing lines in the constructor body, combine those > > with the auto-generated assignments > > > > > > Which is why I'm thinking a first implementation would be reasonable > > which just took this approach: > > > > * "new class" can either have an argument list or a use() statement, not > > both > > * the use() statement generates a constructor at the top of the class > > * if the class body already contains a constructor, the compiler will > > complain that you have two methods named "__construct" > > Ah, fair enough. I'd want to see a better error message than that (which > would be confusing for people who don't know what they're looking for), but > otherwise that's a reasonable first iteration. Especially as I can't > recall when I last had an anon class constructor that was doing anything > other than manual closures. :-) > I created this draft RFC to help move things forward: https://wiki.php.net/rfc/syntax-to-capture-variables-when-declaring-anonymous-classes Please let me know your early thoughts and I'd be happy to move it to "under discussion". I'd also need someone for the implementation as I doubt I'll be able to write it myself in a reasonable time! Cheers, Nicolas
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Thu, Mar 16, 2023, at 6:05 PM, Rowan Tommins wrote: > On 16/03/2023 22:14, Larry Garfield wrote: >> Wouldn't the functionality described boil down to essentially just >> materializing into a few extra lines in the constructor? At least to my >> ignorant non-engine brain it seems straightforward to have this: >> >> $a = 1; >> $b = 2; >> $c = 3; >> >> $o = new class ($a, $b) use ($c) { >>public function __construct(private int $a, private int $b) {} >>public function something() {} >> } >> >> Desugar to this: >> >> $c = class ($a, $b) use ($c) { >>private $c; >>public function __construct(private int $a, private int $b) { >> $this->c = 3; // By value only, so this should be fine? >>} >>public function something() {} >> } > > > Not quite - as Ilija pointed out, the class definition gets compiled > once, but the capture needs to happen for every instance, with > (potentially) different values of $c. In other words, $c needs to be > injected as a constructor argument, not a constant in the class definition. > > That's still fine, in principle - you can compile to this: > > $o = class ($a, $b, $c) { >public function __construct(private int $a, private int $b, private $c) { >} >public function something() {} > } > > Or once constructor promotion is de-sugared as well, this: > > $o = class ($a, $b, $c) { >private int $a; >private int $b; >private $c; > >public function __construct($a, $b, $c) { > $this->a = $a; // from constructor promotion > $this->b = $b; // from constructor promotion > $this->c = $c; // from use() statement > // other lines from body of constructor go here >} >public function something() {} > } > > > It just introduces a lot of extra cases to handle: > > * If there's no constructor, create one > * If there is a constructor with other arguments, merge the argument > lists; since there will then be an explicit argument list to "new > class()", merge those lists as well > * Maybe different handling if those other arguments are already using > constructor promotion, as in this example > * If there are existing lines in the constructor body, combine those > with the auto-generated assignments > > > Which is why I'm thinking a first implementation would be reasonable > which just took this approach: > > * "new class" can either have an argument list or a use() statement, not > both > * the use() statement generates a constructor at the top of the class > * if the class body already contains a constructor, the compiler will > complain that you have two methods named "__construct" Ah, fair enough. I'd want to see a better error message than that (which would be confusing for people who don't know what they're looking for), but otherwise that's a reasonable first iteration. Especially as I can't recall when I last had an anon class constructor that was doing anything other than manual closures. :-) --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On 16/03/2023 22:14, Larry Garfield wrote: Wouldn't the functionality described boil down to essentially just materializing into a few extra lines in the constructor? At least to my ignorant non-engine brain it seems straightforward to have this: $a = 1; $b = 2; $c = 3; $o = new class ($a, $b) use ($c) { public function __construct(private int $a, private int $b) {} public function something() {} } Desugar to this: $c = class ($a, $b) use ($c) { private $c; public function __construct(private int $a, private int $b) { $this->c = 3; // By value only, so this should be fine? } public function something() {} } Not quite - as Ilija pointed out, the class definition gets compiled once, but the capture needs to happen for every instance, with (potentially) different values of $c. In other words, $c needs to be injected as a constructor argument, not a constant in the class definition. That's still fine, in principle - you can compile to this: $o = class ($a, $b, $c) { public function __construct(private int $a, private int $b, private $c) { } public function something() {} } Or once constructor promotion is de-sugared as well, this: $o = class ($a, $b, $c) { private int $a; private int $b; private $c; public function __construct($a, $b, $c) { $this->a = $a; // from constructor promotion $this->b = $b; // from constructor promotion $this->c = $c; // from use() statement // other lines from body of constructor go here } public function something() {} } It just introduces a lot of extra cases to handle: * If there's no constructor, create one * If there is a constructor with other arguments, merge the argument lists; since there will then be an explicit argument list to "new class()", merge those lists as well * Maybe different handling if those other arguments are already using constructor promotion, as in this example * If there are existing lines in the constructor body, combine those with the auto-generated assignments Which is why I'm thinking a first implementation would be reasonable which just took this approach: * "new class" can either have an argument list or a use() statement, not both * the use() statement generates a constructor at the top of the class * if the class body already contains a constructor, the compiler will complain that you have two methods named "__construct" Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Thu, Mar 16, 2023, at 5:06 PM, Rowan Tommins wrote: > On 16/03/2023 17:59, Nicolas Grekas wrote: >> We could define the "use" as declaring + setting the properties before >> the constructor is called, if any. >> But I'm also fine making both constructs conflict: when there is a >> constructor, the boilerplate saved by the "use" becomes really low. >> No strong opinion either. There could be other factors to consider. > > > The main advantage of generating an actual constructor is that no change > is needed to the shared object initialization code, which could be > complex and even have performance impact. The only new logic would be in > compiling the class entry and putting the arguments into the "new > class(...)" statement. > > The more I think about it, the more I'm leaning to just blocking custom > constructors, and saying you can either have the use() short-hand, or > you can have custom initialization. Additional functionality can always > be added in later if someone comes up with a clean implementation and a > good use case. Wouldn't the functionality described boil down to essentially just materializing into a few extra lines in the constructor? At least to my ignorant non-engine brain it seems straightforward to have this: $a = 1; $b = 2; $c = 3; $o = new class ($a, $b) use ($c) { public function __construct(private int $a, private int $b) {} public function something() {} } Desugar to this: $c = class ($a, $b) use ($c) { private $c; public function __construct(private int $a, private int $b) { $this->c = 3; // By value only, so this should be fine? } public function something() {} } --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On 16/03/2023 17:59, Nicolas Grekas wrote: We could define the "use" as declaring + setting the properties before the constructor is called, if any. But I'm also fine making both constructs conflict: when there is a constructor, the boilerplate saved by the "use" becomes really low. No strong opinion either. There could be other factors to consider. The main advantage of generating an actual constructor is that no change is needed to the shared object initialization code, which could be complex and even have performance impact. The only new logic would be in compiling the class entry and putting the arguments into the "new class(...)" statement. The more I think about it, the more I'm leaning to just blocking custom constructors, and saying you can either have the use() short-hand, or you can have custom initialization. Additional functionality can always be added in later if someone comes up with a clean implementation and a good use case. Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
> > To overcome the issues spotted in the thread, what about doing some sort > > of CPP instead of autocapture? > > > > new class (...$arguments) use ($outer) extends Foo { > > public function getIt() { > > return $this->outer; > > } > > } > > > > This would be the equivalent of this: > > > > new class ($outer, ...$arguments) extends Foo { > > public function __construct(public mixed $outer, ...$arguments) { > > parent::__construct(...$arguments); > > } > > public function getIt() { > > return $this->outer; > > } > > } > > > > > I was actually just thinking about exactly that approach, and wondering it > would be possible to do it entirely as an AST rewrite. > > My only uncertainty so far is what to do with an actual constructor in the > class, like this: > > new class($custom) use ($captured) { > public function __construct($custom) { > // Duplicate definition error? > // Silently renamed and called from the generated constructor? > // Merged into the body after the generated lines? > } > } > > Forbidding it wouldn't be the worst restriction, but if there was some > per-instance setup logic needed, not being able to write a constructor body > might be a pain. > We could define the "use" as declaring + setting the properties before the constructor is called, if any. But I'm also fine making both constructs conflict: when there is a constructor, the boilerplate saved by the "use" becomes really low. No strong opinion either. There could be other factors to consider. > > And we could also allow this for better type expressivity: > > new class (...$arguments) use (private int $outer) extends Foo { > > // ... > > } > > > > > I was going to suggest an "as" clause, similar to traits, which would also > allow naming the property differently from the source variable: > > $foo = 42; > $name = 'Bob'; > $class = new class use ($foo as private int $counter, $name as readonly > string) {} > I like that!
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Thu, 16 Mar 2023 at 09:28, Nicolas Grekas wrote: > > To overcome the issues spotted in the thread, what about doing some sort > of CPP instead of autocapture? > > new class (...$arguments) use ($outer) extends Foo { > public function getIt() { > return $this->outer; > } > } > > This would be the equivalent of this: > > new class ($outer, ...$arguments) extends Foo { > public function __construct(public mixed $outer, ...$arguments) { > parent::__construct(...$arguments); > } > public function getIt() { > return $this->outer; > } > } > I was actually just thinking about exactly that approach, and wondering it would be possible to do it entirely as an AST rewrite. My only uncertainty so far is what to do with an actual constructor in the class, like this: new class($custom) use ($captured) { public function __construct($custom) { // Duplicate definition error? // Silently renamed and called from the generated constructor? // Merged into the body after the generated lines? } } Forbidding it wouldn't be the worst restriction, but if there was some per-instance setup logic needed, not being able to write a constructor body might be a pain. > And we could also allow this for better type expressivity: > new class (...$arguments) use (private int $outer) extends Foo { > // ... > } > I was going to suggest an "as" clause, similar to traits, which would also allow naming the property differently from the source variable: $foo = 42; $name = 'Bob'; $class = new class use ($foo as private int $counter, $name as readonly string) {} Equivalent to: $foo = 42; $name = 'Bob'; $class = new class($foo, $name) { public function __construct(private int $counter, public readonly string $name) {} } Or in full: $foo = 42; $name = 'Bob'; $class = new class($foo, $name) { private int $counter; public readonly string $name; public function __construct(int $counter, string $name) { $this->counter = $counter; $this->name = $name; } } Regards, -- Rowan Tommins [IMSoP]
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
Hi Rowan, I have been pondering for a while how to improve the anonymous class > syntax to allow "capturing" of values from the outer scope, and came up > with the idea of a special variable marker for "lexically captured > variable" - instead of $foo, you would write $!foo or $^foo (I quite > like the "upwardness" of $^). > To overcome the issues spotted in the thread, what about doing some sort of CPP instead of autocapture? new class (...$arguments) use ($outer) extends Foo { public function getIt() { return $this->outer; } } This would be the equivalent of this: new class ($outer, ...$arguments) extends Foo { public function __construct(public mixed $outer, ...$arguments) { parent::__construct(...$arguments); } public function getIt() { return $this->outer; } } And we could also allow this for better type expressivity: new class (...$arguments) use (private int $outer) extends Foo { // ... } Nicolas
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Tue, Mar 14, 2023, at 10:28 PM, Alexandru Pătrănescu wrote: > On Wed, Mar 15, 2023 at 1:09 AM Rowan Tommins > wrote: > >> On 14/03/2023 22:54, Larry Garfield wrote: >> >> Well, a large part of my resistance to automatic capture is that it >> makes variable scope less visible at a glance. This avoids that by still >> having a marker for "I am from another scope", but a much less verbose >> one than the current use() clause. >> > > How about first implementing use() for anonymous classes first? > Something like: > > function foo(int $outer) { > return new class() use($outer) { > public function getIt() { > return $outer; > } > }; > } > > This would help with the main problem you expressed and be a less concern > for the future. > > It's a different direction from what you suggested but it might be a lot > harder to pass a $^ syntax or similar. > > Regards, > Alex For the record, while the auto-capture RFC[1] didn't reach a 2/3 majority to pass, it did have a strong majority support (62%). Given that, I think it unlikely that a "require verbose manual capture in more places" proposal would fair that well. [1] https://wiki.php.net/rfc/auto-capture-closure --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Wed, Mar 15, 2023 at 8:38 AM Rowan Tommins wrote: > > On 15 March 2023 03:28:39 GMT, "Alexandru Pătrănescu" > wrote: > > >How about first implementing use() for anonymous classes first? > >Something like: > > > >function foo(int $outer) { > > return new class() use($outer) { > > public function getIt() { > > return $outer; > > } > > }; > >} > > > I think it's a lot less clear exactly where the variable is being imported to > that way. Would you still be able to overwrite $outer as a normal local > variable in that function scope? Would it return to its captured value every > time the function runs? It's different from capturing into a function, > because you're not just adding a variable to a single existing scope. > > Just allowing the captured var for property initialisation would be less > ambiguous, but having to list it in use() and then in the property list would > be the same amount of code as you can currently get to with constructor > promotion (see my first example in the thread). > > From what Ilija has pointed out, the implementation of actually getting the > values into the right place is going to be harder than handling the syntax > anyway. My current feeling is that if it's possible at all, it will end up as > a shorthand for that constructor example: declaring normal properties, and > populating them as the instance is initialised. > > Regards, > > -- > Rowan Tommins > [IMSoP] > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > > The outer variable would be captured by value, and carried around with > the instance, like existing closures. For what its worth, there are ways to get references inside values: https://3v4l.org/5q93F Other than pointing that out, I like it. -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On 15 March 2023 03:28:39 GMT, "Alexandru Pătrănescu" wrote: >How about first implementing use() for anonymous classes first? >Something like: > >function foo(int $outer) { > return new class() use($outer) { > public function getIt() { > return $outer; > } > }; >} I think it's a lot less clear exactly where the variable is being imported to that way. Would you still be able to overwrite $outer as a normal local variable in that function scope? Would it return to its captured value every time the function runs? It's different from capturing into a function, because you're not just adding a variable to a single existing scope. Just allowing the captured var for property initialisation would be less ambiguous, but having to list it in use() and then in the property list would be the same amount of code as you can currently get to with constructor promotion (see my first example in the thread). From what Ilija has pointed out, the implementation of actually getting the values into the right place is going to be harder than handling the syntax anyway. My current feeling is that if it's possible at all, it will end up as a shorthand for that constructor example: declaring normal properties, and populating them as the instance is initialised. Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Wed, Mar 15, 2023 at 1:09 AM Rowan Tommins wrote: > On 14/03/2023 22:54, Larry Garfield wrote: > > Well, a large part of my resistance to automatic capture is that it > makes variable scope less visible at a glance. This avoids that by still > having a marker for "I am from another scope", but a much less verbose > one than the current use() clause. > How about first implementing use() for anonymous classes first? Something like: function foo(int $outer) { return new class() use($outer) { public function getIt() { return $outer; } }; } This would help with the main problem you expressed and be a less concern for the future. It's a different direction from what you suggested but it might be a lot harder to pass a $^ syntax or similar. Regards, Alex
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Tue, Mar 14, 2023 at 7:09 PM Rowan Tommins wrote: > Outside of > those cases, though, there's no reason it should mean anything, just as > $this->foo or self::$foo doesn't mean anything outside a class. In fact, > it could be spelled capture::$foo or $scope->foo rather than just using > new punctuation, if we wanted to encourage that analogy. Would this be allowed in files included in the methods of the anonymous class? `$this->` and `self::` is, and it's a pain point for static analyzers, forcing us to invent things like `@psalm-scope-this` -- Best regards, Bruce Weirdan mailto:weir...@gmail.com -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On 14/03/2023 22:54, Larry Garfield wrote: However, I agree with Ilija that the original proposal to just do it automatically would be better; and if that didn't pass, I have no expectation that an alternate with a funky new syntax would do any better. Well, a large part of my resistance to automatic capture is that it makes variable scope less visible at a glance. This avoids that by still having a marker for "I am from another scope", but a much less verbose one than the current use() clause. I may be in a minority of one on that point, though, for all I know. The other concern is that this introduces a whole new realm of possible behaviors... That's kind of where the closure thought came from - it seemed a very close analogue, so having one syntax for both feels natural. Outside of those cases, though, there's no reason it should mean anything, just as $this->foo or self::$foo doesn't mean anything outside a class. In fact, it could be spelled capture::$foo or $scope->foo rather than just using new punctuation, if we wanted to encourage that analogy. Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
> I guess you'd have to generate a new class entry every time the "new > class" line was run, and inject the extra values into that. > > > If it was limited to capturing scalars and arrays, you could treat it as > a kind of macro expansion, i.e. this ... > > $example = new class { > public $inner = $^outer; > } > > ... could be a sort of sugar for: > > eval( > sprintf( > 'return new class { > public $inner = %s; > };', > var_export($outer, true) > ) > ); > > Which is valid code, if not particularly efficient: https://3v4l.org/sQaUS Unfortunately, PHP isn't really well suited for something like that. Eval'd classes are still request-persistent, so any created object would leak its class structure (which is much bigger and less optimized in terms of memory than the object). Normal anonymous classes: https://3v4l.org/41UGH Anonymous classes created through eval (and thus creating a separate class): https://3v4l.org/Q9eE3 Ilija -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On Tue, Mar 14, 2023, at 3:41 PM, Rowan Tommins wrote: > Hi all, > > I have been pondering for a while how to improve the anonymous class > syntax to allow "capturing" of values from the outer scope, and came up > with the idea of a special variable marker for "lexically captured > variable" - instead of $foo, you would write $!foo or $^foo (I quite > like the "upwardness" of $^). > > To give a simple example, values can only pass into the anonymous class > via its constructor, like this: > > function foo(int $outer) { > return new class($outer) { > public function __construct( > private int $myProp > ) {} > public function getIt() { > return $this->myProp; > } > }; > } > > The idea is that you would instead be able to reference an outer > variable directly anywhere in the declaration, removing a lot of > boilerplate: > > function foo(int $outer) { > return new class { > public function getIt() { > return $^outer; > } > }; > } > > The outer variable would be captured by value, and carried around with > the instance, like existing closures. > > I suggest it is also treated as readonly, and visible strictly in the > lexical definition, not generally in private scope (it couldn't be > referenced from an applied trait, for instance). > > Using it to initialise a property or local variable would allow you to > give it an explicit scope, while still avoiding the constructor: > > function foo(int $outer) { > private $inner = $!outer; > return new class { > public function getIt() { > $this->inner++; > return $this->inner; > } > }; > } > > > It then occurred to me that the same syntax could be used in > multi-statement anonymous functions instead of an explicit use() > statement. This strikes a different balance between conciseness and > explicitness than full automatic capture: > > $before = function($x) use ($y, $z) { > $a = $x * $y; > return do_something($a, $z); > } > $after = function($x) { > $a = $x * $^y; > return do_something($a, $^z); > } > > > To keep this message short, I've put some more examples and thoughts > into a GitHub Gist here: > https://gist.github.com/IMSoP/4157af05c79b3df4c4853f5a58766341 > > I'd be interested to hear anyone's thoughts - is this a promising idea > to explore, or have I gone completely off the rails? Pourque no los dos? Ilija's point about implementation challenges concerns me, but my initial thought is that I quite like it. I've run into the same issue with capturing into anon classes and wished for something like this. As you were describing it, I was also thinking of the potential implications for closures, too. That sounds... kinda fun. :-) However, I agree with Ilija that the original proposal to just do it automatically would be better; and if that didn't pass, I have no expectation that an alternate with a funky new syntax would do any better. The other concern is that this introduces a whole new realm of possible behaviors; like, what does $^foo mean in a non-anon-class context? Does it? Could it be made to mean something in the future? Does it become an alternate syntax for `global`? What else could we do with it? This rabbit hole goes very deep very quickly, and pursuing this for anon classes without considering the longer-term implications seems both impossible (because people will ask) and unwise. --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
On 14/03/2023 21:23, Ilija Tovilo wrote: One thing to note is that, as I've learned recently, anonymous classes can actually be instantiated at a later point with some tricks. https://3v4l.org/2OcmP Huh, that's freaky... I guess this is all a reminder that these really are anonymous *classes*, not anonymous *instances* - at some point, a class entry needs to be generated. I guess you'd have to generate a new class entry every time the "new class" line was run, and inject the extra values into that. If it was limited to capturing scalars and arrays, you could treat it as a kind of macro expansion, i.e. this ... $example = new class { public $inner = $^outer; } ... could be a sort of sugar for: eval( sprintf( 'return new class { public $inner = %s; };', var_export($outer, true) ) ); Which is valid code, if not particularly efficient: https://3v4l.org/sQaUS That doesn't allow for normal object semantics, though, so is probably a non-starter. This could also prove technically challenging. Currently, property defaults are constant ASTs and unique per class (not object). As I understand it, the main reason object properties weren't included in the "new in initializers" RFC was the problem of when side effects would occur: https://wiki.php.net/rfc/new_in_initializers#unsupported_positions That's not an issue here, because we're not *creating* an object, we're "just" resolving the $^outer token to an existing zval, and storing it in the default property table. It might actually be easier to *only* allow capture into property initialisers: 1. Create a new class entry without any property default (or clone a pre-compiled base) 2. Add the captured values to the default property table 3. Create an instance, run its constructor, etc, as currently Or even: 1. Use the existing logic to create an anonymous class entry ignoring the capturing syntax 2. Create an instance, initialise normal defaults, but don't run the constructor yet 3. Push the captured values directly into the *instance* properties 4. Run the constructor I'm probably in way over my head here, so I should probably stop here, in the hope that it will inspire someone more knowledgeable to come up with something workable, because I'd really love for anonymous classes to be more flexible than they are. Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
Hey Rowan On Tue, Mar 14, 2023 at 9:41 PM Rowan Tommins wrote: > > Hi all, > > I have been pondering for a while how to improve the anonymous class > syntax to allow "capturing" of values from the outer scope, and came up > with the idea of a special variable marker for "lexically captured > variable" - instead of $foo, you would write $!foo or $^foo (I quite > like the "upwardness" of $^). > > To give a simple example, values can only pass into the anonymous class > via its constructor, like this: > > function foo(int $outer) { > return new class($outer) { > public function __construct( > private int $myProp > ) {} > public function getIt() { > return $this->myProp; > } > }; > } > > The idea is that you would instead be able to reference an outer > variable directly anywhere in the declaration, removing a lot of > boilerplate: > > function foo(int $outer) { > return new class { > public function getIt() { > return $^outer; > } > }; > } One thing to note is that, as I've learned recently, anonymous classes can actually be instantiated at a later point with some tricks. https://3v4l.org/2OcmP This could be avoided by adding the value to the constructor, but that fully defeats the purpose of your proposal. Holding the value indefinitely would create a leak and also doesn't really work as `createAnonymousClass` could be called multiple times and thus capturing multiple `$value`s. We could also decide to disallow instantiations of anonymous classes in other places, that would probably make most sense. This way the captured value could be attached somewhere in the object and $^foo could access that instead. That being said, I am indeed very skeptical if the added complexity is worth it. ``` // Lexical values could be used to initialse private, protected, or public properties // The same lexical value can be used any number of times $x = 1; $example = new class { private $x = $^x; protected $sharedX = $^x; public $alsoX = $^x; } ``` This could also prove technically challenging. Currently, property defaults are constant ASTs and unique per class (not object). For your case, they would need to be different per instance, which might require quite a bit of refactoring. As for the syntax in closures, that seems a bit more useful to me personally, although I dislike the multi-nesting. Moving code, it might be easy to miss that the number of "^" needs adjustments. I'd personally prefer the previously proposed approach of capturing just by name. Good naming should minimize the risk of clashing variables. Regards, Ilija -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
[PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables
Hi all, I have been pondering for a while how to improve the anonymous class syntax to allow "capturing" of values from the outer scope, and came up with the idea of a special variable marker for "lexically captured variable" - instead of $foo, you would write $!foo or $^foo (I quite like the "upwardness" of $^). To give a simple example, values can only pass into the anonymous class via its constructor, like this: function foo(int $outer) { return new class($outer) { public function __construct( private int $myProp ) {} public function getIt() { return $this->myProp; } }; } The idea is that you would instead be able to reference an outer variable directly anywhere in the declaration, removing a lot of boilerplate: function foo(int $outer) { return new class { public function getIt() { return $^outer; } }; } The outer variable would be captured by value, and carried around with the instance, like existing closures. I suggest it is also treated as readonly, and visible strictly in the lexical definition, not generally in private scope (it couldn't be referenced from an applied trait, for instance). Using it to initialise a property or local variable would allow you to give it an explicit scope, while still avoiding the constructor: function foo(int $outer) { private $inner = $!outer; return new class { public function getIt() { $this->inner++; return $this->inner; } }; } It then occurred to me that the same syntax could be used in multi-statement anonymous functions instead of an explicit use() statement. This strikes a different balance between conciseness and explicitness than full automatic capture: $before = function($x) use ($y, $z) { $a = $x * $y; return do_something($a, $z); } $after = function($x) { $a = $x * $^y; return do_something($a, $^z); } To keep this message short, I've put some more examples and thoughts into a GitHub Gist here: https://gist.github.com/IMSoP/4157af05c79b3df4c4853f5a58766341 I'd be interested to hear anyone's thoughts - is this a promising idea to explore, or have I gone completely off the rails? Regards, -- Rowan Tommins [IMSoP] -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php