Re: [PHP-DEV] Brainstorming idea: inline syntax for lexical (captured) variables

2023-04-14 Thread Rowan Tommins
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

2023-04-13 Thread DANIEL VARGAS MUCCILLO
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

2023-04-13 Thread Michał Marcin Brzuchalski
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

2023-04-13 Thread Rowan Tommins
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

2023-04-13 Thread Nicolas Grekas
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

2023-03-17 Thread Larry Garfield
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

2023-03-16 Thread Rowan Tommins

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

2023-03-16 Thread Larry Garfield
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

2023-03-16 Thread Rowan Tommins

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

2023-03-16 Thread Nicolas Grekas
> > 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

2023-03-16 Thread Rowan Tommins
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

2023-03-16 Thread Nicolas Grekas
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

2023-03-15 Thread Larry Garfield
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

2023-03-15 Thread Robert Landers
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

2023-03-15 Thread Rowan Tommins
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

2023-03-14 Thread Alexandru Pătrănescu
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

2023-03-14 Thread Bruce Weirdan
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

2023-03-14 Thread Rowan Tommins

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

2023-03-14 Thread Ilija Tovilo
> 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

2023-03-14 Thread Larry Garfield
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

2023-03-14 Thread Rowan Tommins

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

2023-03-14 Thread Ilija Tovilo
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

2023-03-14 Thread Rowan Tommins

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