Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Levi Morrison via internals
On Thu, Apr 20, 2023 at 9:57 PM Deleu  wrote:
>
>
>
> On Thu, Apr 20, 2023 at 8:23 PM Levi Morrison via internals 
>  wrote:
>>
>> I'm going to stop here. Two big things:
>>
>>  1. I think reducing verbosity can be left for the future. We don't
>> have to solve that right now.
>
>
>
>> What happens if I pass a short-closure?
>>
>> takeTwo(fn ($x, $y) => $x + $y);
>>
>> I would be annoyed if I had to write the type info, but particularly
>> the return type.
>
>
>  Sorry for the unhelpful email, but does anybody else see the irony here? 
> It's just too funny to not be mentioned 
>
> --
> Marco Deleu

Sure, I get that ^_^ But the difference is that there are quite a few
ways we can solve the first verbosity (allowing `fn` instead of
`callable`, allowing type aliases which could also be useful for
unions, etc), and only things that seem hard to solve the second one
(static type inference? delayed type checks?)

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Deleu
On Thu, Apr 20, 2023 at 8:23 PM Levi Morrison via internals <
internals@lists.php.net> wrote:

> I'm going to stop here. Two big things:
>
>  1. I think reducing verbosity can be left for the future. We don't
> have to solve that right now.
>


What happens if I pass a short-closure?
>
> takeTwo(fn ($x, $y) => $x + $y);
>
> I would be annoyed if I had to write the type info, but particularly
> the return type.
>

 Sorry for the unhelpful email, but does anybody else see the irony here?
It's just too funny to not be mentioned 

-- 
Marco Deleu


[PHP-DEV] Expansion of PHP Symbols?

2023-04-20 Thread Deleu
Hi Internals, sorry for the potential long email, but I can't sleep and I
want to dump this out of my brain.

Just a quick intro, let's recap PHP simple nature with this index.php file:

```
https://wiki.php.net/rfc/core-autoloading) and Callable Interfaces (
https://externals.io/message/120083) and particularly this comment
https://externals.io/message/120083#120088 which I'll take a snippet here:

Mainly the type alias question. Every time I see callable types discussed,
> it immediately sidetracks into "how do we make that less fugly to write,
> because callable types are naturally very verbose?" That leads directly to
> typedefs/type aliases, which take one of two forms:



type TwoInts = callable(int $x, int $y): int
> type LinkedResponse = ResponseInterface


> which raises autoloading questions and means a dependency on a type
> defined in another package, in many cases


Here I want to raise the discussion about the little I know about PHP
symbols. If a symbol `interface Foo` is discovered and registered by PHP,
it will be usable as an interface throughout its entire execution. If the
symbol is not discovered and is used, it will trigger autoloading as a
last-chance before fatal error.

Can `type Number = int|number;` be a symbol?
Can `type TwoInts = callable(int $x, int $y): int;` be a symbol?
and lastly, can `function bar() {}` be a symbol?

Here is how I see that unfolding:

file-1.php
```

```

file-2.php
```

```

file-3.php
```

```

How do we solve this?
- Include/Require
- Custom Autoloading
- Composer/PSR-4

Sounds familiar?

PHP already has a namespace to solve for grouping of types such that
`\Foo\Bar` and `\Bar\Bar` can already co-exist because they are two
different symbols. We already rely on classes/interfaces/enums defined on
third-party packages, why not make type aliases work the same?
And if functions could be symbols, it would work out the same as well.

One limitation I see is that symbols cannot have conflicts.
Enum/Interfaces/Classes cannot be named exactly the same under the exact
same namespace. Type Alias would follow the same limitation. Function would
be able to escape one part of that limitation if the engine could prefix
every function name with `f_` or `fn_` when registering its symbol, making
it backward compatible and allowing `class Foo` and `function Foo`
co-exist, but not two functions called `Foo` (which is already an error
anyway).

Now one questionable user experience I see is defining:
- 1 Class = 1 File
- 1 Interface = 1 File
- 1 Enum = 1 File (this is already annoying)
- 1 function = 1 file
- 1 symbol = 1 file

But this user experience does not come from PHP's nature, but instead it
comes from Composer/PSR-4 because PSR-4 maps namespaces to directories and
symbols to files. We need a static analyser to scan our entire project,
discover every symbol and create a mapping such as:
- Symbol Class_Foo -> x.php
- Symbol Interface_Bar -> x.php
- Symbol Enum_Options -> y.php
- Symbol Enum_ExtraOptions -> y.php
- Symbol fn_foo -> z.php

so that our registered autoload can include/require the necessary file when
the symbol triggers an autoload. This static analyser already exists and is
called `composer dump-autoload`. It just has not implemented a PSR-X which
allows for this pattern?

In conclusion

PHP Scripts are very simple and dummy things and it already has a
limitation of symbol discovery. We have already built tools to work with
that limitation around autoloading/psr/composer. We could extend the PHP
Symbol system to include functions and type alises. If this can be done in
a backward-compatible manner, we can already integrate those into PSR-4
from day one. Lastly, we lift the social-construct limitation of 1 file = 1
symbol with PSR and Composer since PHP never really had this limitation
built-in. We come out of it with 3 major wins (from my pov):

- Function autoloading
- Type Aliasing
- Never creating 3 files for 3 Enums again

If you managed to read up to here, I apologize for late confessing I know
nearly nothing about PHP internals codebase. Is this obviously wrong and am
I just hallucinating a new awesome PHP version here?

-- 
Marco Deleu


Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Levi Morrison via internals
On Thu, Apr 20, 2023 at 11:25 AM Larry Garfield  wrote:
>
> Hi folks.  This is a pre-discussion, in a sense, before a formal RFC.  
> Nicolas Grekas and I have been kicking around some ideas for how to address 
> the desire for typed callables, and have several overlapping concepts to 
> consider.  Before going down the rabbit hole on any of them we want to gauge 
> the general feeling about the approaches to see what is worth pursuing.
>
> We have three "brain dump" RFCs on this topic, although these are all still 
> in super-duper early stages so don't sweat the details in them at this point. 
>  We just want to discuss the basic concepts, which I have laid out below.
>
> https://wiki.php.net/rfc/allow_casting_closures_into_single-method_interface_implementations
> https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
> https://wiki.php.net/rfc/structural-typing-for-closures
>
> ## The problem
>
> function takeTwo(callable $c): int
> {
> return $c(1, 2);
> }
>
> Right now, we have no way to statically enforce that $c is a callable that 
> takes 2 ints and returns an int.  We can document it, but that's it.
>
> There is one loophole, in that an interface may require an __invoke() method:
>
> interface TwoInts
> {
> public function __invoke(int $a, int $b): int;
> }
>
> And then a class may implement TwoInts, and takeTwo() can type against 
> TwoInts.  However, that works only for classes, which are naturally 
> considerably more verbose than a simple closure and represent only a subset 
> of the possible callable types.
>
> The usual discussion has involved a way to specify a callable type's 
> signature, like so:
>
> function takeTwo(callable(int $a, int $b): int $c)
> {
>   return $c(1, 2);
> }
>
> But that runs quickly into the problem of verbosity, reusability, and type 
> aliases, and the discussion usually dies there.
>

I'm going to stop here. Two big things:

 1. I think reducing verbosity can be left for the future. We don't
have to solve that right now.
 2. I think there's another more important reason previous attempts
failed: they will inevitably burden the programmer without type
inference.For the moment, let's assume this signature:

function takeTwo(callable(int $x, int $y): int $c);

What happens if I pass a short-closure?

takeTwo(fn ($x, $y) => $x + $y);

I would be annoyed if I had to write the type info, but particularly
the return type.

Today, if I just used a static analysis tool, there's no problem:

/** @param callable(int $x, int $y): int $c */
function takeTwo(callable $c);
takeTwo(fn ($x, $y) => $x + $y);

 3. And another reason they failed: callables are going to want
generic types pretty commonly. Think array_filter, array_map, etc.

So, I think these brain dump RFCs are all focusing on the wrong problems.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Larry Garfield
On Thu, Apr 20, 2023, at 9:47 PM, David Gebler wrote:
> On Thu, Apr 20, 2023 at 6:25 PM Larry Garfield 
> wrote:
>
>> ## The options
>>
>> There's three ways we've come up with that this design could be
>> implemented.  In concept they're not mutually exclusive, so we could do
>> one, two, or three of these.  Figuring out which approach would get the
>> most support is the purpose of this thread.
>>
>
> My initial feelings based on the options laid out is that anything which
> can't support FCCs in the manner of strlen(...) is probably a non-starter
> in terms of language design. Changes like this are fundamentally about
> making things simpler, more concise and more convenient for users, not
> drip-feeding a stream of "and here's yet another way of working with..."
> features across releases.
>
> Structural typing option seems like the easiest to implement in the engine
> (correct me if I'm wrong?) and probably the best syntax for the user within
> the interface approach. But then do we really want to introduce new runtime
> checks and complexity when the general trend of the language has been in
> the opposite direction? I imagine probably not.

Our assumption is the opposite: Structural typing would be the hardest to 
implement, and have the largest performance risk, but be the nicest/most 
convenient for developers.

> So out of the three, I lean towards adding castTo() to Closure and it maybe
> raises a to-be-determined Throwable if the closure is already bound? It's
> not as friendly for the user as the other options but it seems like the
> most workable, it delivers value and it most closely fits within the
> existing way of working with all types of closure today.
>
> -Dave

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread David Gebler
On Thu, Apr 20, 2023 at 6:25 PM Larry Garfield 
wrote:

> ## The options
>
> There's three ways we've come up with that this design could be
> implemented.  In concept they're not mutually exclusive, so we could do
> one, two, or three of these.  Figuring out which approach would get the
> most support is the purpose of this thread.
>

My initial feelings based on the options laid out is that anything which
can't support FCCs in the manner of strlen(...) is probably a non-starter
in terms of language design. Changes like this are fundamentally about
making things simpler, more concise and more convenient for users, not
drip-feeding a stream of "and here's yet another way of working with..."
features across releases.

Structural typing option seems like the easiest to implement in the engine
(correct me if I'm wrong?) and probably the best syntax for the user within
the interface approach. But then do we really want to introduce new runtime
checks and complexity when the general trend of the language has been in
the opposite direction? I imagine probably not.

So out of the three, I lean towards adding castTo() to Closure and it maybe
raises a to-be-determined Throwable if the closure is already bound? It's
not as friendly for the user as the other options but it seems like the
most workable, it delivers value and it most closely fits within the
existing way of working with all types of closure today.

-Dave


Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Larry Garfield
On Thu, Apr 20, 2023, at 8:26 PM, Michał Marcin Brzuchalski wrote:
> Hi
>
>> There is one loophole, in that an interface may require an __invoke()
>> method:
>>
>> interface TwoInts
>> {
>> public function __invoke(int $a, int $b): int;
>> }
>>
>>
> I was playing around with the code and parser for this in 2020 but my idea
> was to introduce a new syntax that is inspired by C# - Delegates [1]
>
> delegate Reducer (?int $sum, int $item = 0): int;
>
> class Foo implements Reducer {
> public function __invoke(?int $sum, int $item = 0): int { }
> }
> function reduce(Reducer $reducer) {
> var_dump($reducer(0, 5));
> }
> reduce(new Foo());
> reduce(fn(?int $sum, int $item = 0): int => 8);
>
> At the same time, I assumed structural typing for closures would be used.
> I assumed the delegate will resolve into
> interface Reducer {
> public function __invoke(?int $sum, int $item = 0): int {}
> }

This is effectively the same as the "typedef" version of callable types from my 
email to Dan a moment ago.  See there for the challenges.

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Michał Marcin Brzuchalski
Hi

> There is one loophole, in that an interface may require an __invoke()
> method:
>
> interface TwoInts
> {
> public function __invoke(int $a, int $b): int;
> }
>
>
I was playing around with the code and parser for this in 2020 but my idea
was to introduce a new syntax that is inspired by C# - Delegates [1]

delegate Reducer (?int $sum, int $item = 0): int;

class Foo implements Reducer {
public function __invoke(?int $sum, int $item = 0): int { }
}
function reduce(Reducer $reducer) {
var_dump($reducer(0, 5));
}
reduce(new Foo());
reduce(fn(?int $sum, int $item = 0): int => 8);

At the same time, I assumed structural typing for closures would be used.
I assumed the delegate will resolve into
interface Reducer {
public function __invoke(?int $sum, int $item = 0): int {}
}

I also noticed that once checked closure doesn't have to be checked against
the argument types and return type because it won't change which gives some
possibility to cache this type check.


> The usual discussion has involved a way to specify a callable type's
> signature, like so:
>
> function takeTwo(callable(int $a, int $b): int $c)
> {
>   return $c(1, 2);
> }
>
> But that runs quickly into the problem of verbosity, reusability, and type
> aliases, and the discussion usually dies there.
>
>
This is why initially I thought about Delegates as in C# there are not type
aliases.
The delegate essentially resolves to an interface with `__invoke(?int $sum,
int $item = 0): int` method.


> ### Structural typing for closures
>
> The third option would necessitate having similar logic in the engine to
> the first.  In this case, we take a "structural typing" approach to
> closures; that is, "if the types match at runtime, it must be OK."  This is
> probably closest to the earlier proposals for a `callable(int $x, int $y):
> int` syntax (which would by necessity have to be structural), but
> essentially uses interfaces as the type alias.
>
> function takeTwo(TwoInts $c): int
> {
> return $c(1, 2);
> }
>
> $result = takeTwo(fn(int $x, int $y): int => $x + $y);
>

I'd love to see this happening.

[1]
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/

Cheers,
Michał Marcin Brzuchalski


Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Larry Garfield
On Thu, Apr 20, 2023, at 7:30 PM, Dan Ackroyd wrote:
> On Thu, 20 Apr 2023 at 18:25, Larry Garfield  wrote:
>>
>> Hi folks.  This is a pre-discussion, in a sense, before a formal RFC.
>
>
> Hi Larry,
>
> The "Allow casting closures into single-method interface
> implementations" one seems a complete non-starter, as that seems
> really hard to work with. You'd have to do lots of "wiring up" to do
> any significant amount of programming.

That's my concern as well, and part of what led me to suggest the structural 
typing approach.  

> "Allow Closures to Declare Interfaces they Implement"
>
> That sounds bad as it doesn't allow arbitrary functions to be used as 
> callables.

Yes.  Which is why I think it would make the most sense when combined with one 
of the other two options, so it's a sort of performance optimization for the 
general case.

>> We feel that the interface-based approach is strong
>
> All three of them are using interfaces...?

I'm referring to the general idea of this thread, which is "an interface with 
__invoke is how you define a callable type."  Everything else here is a 
variation on that basic premise.

> But if you mean the "Structural Typing for Closures" one, then I'd
> probably agree. But as currently proposed it seems like a hack, that
> would be predictably regrettable in a couple of years.

In what way?

>> and a good way forward for getting typed callables
>> without a bunch of dependent features needed first.
>
> Maybe list what you think the dependent features are, so that there
> isn't confusion about them, but I suspect that we're going to not
> agree on how languages should be designed and evolve.

Mainly the type alias question.  Every time I see callable types discussed, it 
immediately sidetracks into "how do we make that less fugly to write, because 
callable types are naturally very verbose?"  That leads directly to 
typedefs/type aliases, which take one of two forms:

type TwoInts = callable(int $x, int $y): int
type LinkedResponse = ResponseInterface

which raises autoloading questions and means a dependency on a type defined in 
another package, in many cases.  Or:

use callable(int $x, int $y): int as TwoInts
use ResponseInterface as LinkedResponse

Which would be file-local, much like class "use" statements are.  That avoids 
the autoload and dependency problem, at the cost of having to retype that 
frickin' thing in every file where it's relevant.  In many cases, that could be 
dozens or hundreds of files repeating that line.

And that's where the discussion usually dies off.

As noted, this is still a form of structural typing, which means the function 
call process necessarily gets more complex (for callables).

Using existing interfaces for callable definitions side steps the 
implementation challenges of type aliases/typedefs, since once you have an 
object tagged with an interface (via any of the mechanisms described), its 
behavior is already very well-defined and predictable.

> Although I really want to see typed callables, and other forms of type
> aliasing, as they would be huge improvements in being able to write
> code that is easy to reason about and maintain, I don't want to seem
> them as soon as possible, having taken short-cuts against good
> language design.
>
> "No is temporary, yes is forever".

I'm happy to see forward motion on any front.  If the result of this thread is 
that someone gets incentivized to finally figure out callable types for 
realsies without an interface, I'd sleep happy with that result.  But this 
gives us something concrete to chew on, which we have so far lacked.

--Larry Garfield

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Michał Marcin Brzuchalski
Hi Tim,

czw., 20 kwi 2023 o 16:39 Tim Düsterhus  napisał(a):

> ...
> But please no entirely new syntax with braces as it currently is shown
> in the examples in the RFC.
>

Then we should vote for syntax. Personally, I prefer braces here
because it doesn't look like a regular function call allowing easily to
distinguish
between two different things.

Cheers,
Michał Marcin Brzuchalski


Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Larry Garfield
On Thu, Apr 20, 2023, at 7:10 PM, Deleu wrote:
> On Thu, Apr 20, 2023 at 2:25 PM Larry Garfield 
> wrote:
>
>> But that runs quickly into the problem of verbosity, reusability, and type
>> aliases, and the discussion usually dies there.
>>
>
> Just out of curiosity, was there ever a discussion thread dedicated for
> Type aliases?  I couldn't find it on externals.io and I was curious to know
> what are the challenges there since a lot of time and effort seem to have
> been put on trying to sidestep it.

Not that I recall, on list.  There has been discussion on and off in various 
chat rooms over the last 3-ish years.  The general pattern seems to be "that 
would be cool and useful!  But how do we define them, and how does autoloading 
work for something that's a one liner?"  And since no one has a good answer for 
that, it kinda dies there.

I wouldn't expect a formal discussion thread on it until someone has a solid 
proposal that addresses those questions that they want to run up the flag pole. 
 (Not that I think that's a good process, but that's how things work right now, 
mostly.)

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Dan Ackroyd
On Thu, 20 Apr 2023 at 18:25, Larry Garfield  wrote:
>
> Hi folks.  This is a pre-discussion, in a sense, before a formal RFC.


Hi Larry,

The "Allow casting closures into single-method interface
implementations" one seems a complete non-starter, as that seems
really hard to work with. You'd have to do lots of "wiring up" to do
any significant amount of programming.

"Allow Closures to Declare Interfaces they Implement"

That sounds bad as it doesn't allow arbitrary functions to be used as callables.

> We feel that the interface-based approach is strong

All three of them are using interfaces...?

But if you mean the "Structural Typing for Closures" one, then I'd
probably agree. But as currently proposed it seems like a hack, that
would be predictably regrettable in a couple of years.

> and a good way forward for getting typed callables
> without a bunch of dependent features needed first.

Maybe list what you think the dependent features are, so that there
isn't confusion about them, but I suspect that we're going to not
agree on how languages should be designed and evolve.

Although I really want to see typed callables, and other forms of type
aliasing, as they would be huge improvements in being able to write
code that is easy to reason about and maintain, I don't want to seem
them as soon as possible, having taken short-cuts against good
language design.

"No is temporary, yes is forever".

cheers
Dan
Ack

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Deleu
On Thu, Apr 20, 2023 at 2:25 PM Larry Garfield 
wrote:

> But that runs quickly into the problem of verbosity, reusability, and type
> aliases, and the discussion usually dies there.
>

Just out of curiosity, was there ever a discussion thread dedicated for
Type aliases?  I couldn't find it on externals.io and I was curious to know
what are the challenges there since a lot of time and effort seem to have
been put on trying to sidestep it.

-- 
Marco Deleu


[PHP-DEV] [Discussion] Callable types via Interfaces

2023-04-20 Thread Larry Garfield
Hi folks.  This is a pre-discussion, in a sense, before a formal RFC.  Nicolas 
Grekas and I have been kicking around some ideas for how to address the desire 
for typed callables, and have several overlapping concepts to consider.  Before 
going down the rabbit hole on any of them we want to gauge the general feeling 
about the approaches to see what is worth pursuing.

We have three "brain dump" RFCs on this topic, although these are all still in 
super-duper early stages so don't sweat the details in them at this point.  We 
just want to discuss the basic concepts, which I have laid out below.

https://wiki.php.net/rfc/allow_casting_closures_into_single-method_interface_implementations
https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
https://wiki.php.net/rfc/structural-typing-for-closures

## The problem

function takeTwo(callable $c): int
{
return $c(1, 2);
}

Right now, we have no way to statically enforce that $c is a callable that 
takes 2 ints and returns an int.  We can document it, but that's it.  

There is one loophole, in that an interface may require an __invoke() method:

interface TwoInts
{
public function __invoke(int $a, int $b): int;
}

And then a class may implement TwoInts, and takeTwo() can type against TwoInts. 
 However, that works only for classes, which are naturally considerably more 
verbose than a simple closure and represent only a subset of the possible 
callable types.

The usual discussion has involved a way to specify a callable type's signature, 
like so:

function takeTwo(callable(int $a, int $b): int $c) 
{
  return $c(1, 2);
}

But that runs quickly into the problem of verbosity, reusability, and type 
aliases, and the discussion usually dies there.

## The alternative

What we propose is to instead lean into the interface approach.  Specifically, 
recall that all closures in PHP are actually implemented as classes in the 
engine.  That is:

$f = fn(int $x, int $y): int => $x + $y;

actually turns into (approximately) this in the engine:

$f = new class extends \Closure
{
public function __invoke(int $x, int $y): int
{
return $x + $y;
}
}

(It doesn't do syntax translation but that's effectively what the engine does.)

So all that's really missing is a way for arbitrary closures to denote that 
they implement an interface, and then they can be used wherever an interface is 
required.  That neatly sidesteps the verbosity and reusability issues, and 
since interfaces are already well-understood there's no need to wait for type 
aliases.

It would not support the old-style funky callables like a function string or 
[$obj, 'method'], but with the advent of first-class-callables those are no 
longer recommended anyway so not supporting them is probably a good thing.

The same would also work for property types, which can easily type against an 
interface.  That would mostly sidestep the current limitation of typing a 
property as `callable`, since you could provide a more-specific type instead 
for a double-win.

## The options

There's three ways we've come up with that this design could be implemented.  
In concept they're not mutually exclusive, so we could do one, two, or three of 
these.  Figuring out which approach would get the most support is the purpose 
of this thread.

### castTo

The first is to add a castTo() method to Closure.  That would produce a new 
object that has the same logic as the closure, but explicitly implements the 
interface.

That is, this:

$fn2 = $fn->castTo(TwoInts::class);

Is roughly logically equivalent to:

$fn2 = new class($fn) implements TwoInts {
public function __construct(private callable $fn) {}

public function __invoke(int $a, int $b): int
{
return $this->fn(func_get_args();
}
};

(Whether that's what the implementation actually does or if it's smarter about 
it is an open question.)

In theory, this would also support any single-method interface, not just those 
using __invoke().  The other options below would not support that.

This does have a number of open edge cases, like what to do with a closure that 
is already bound to an object.

### Function interfaces

The second option is to allow closures to declare up front what interfaces they 
implement.  So:

$f = fn(int $x, int $y): int implements TwoInts => $x + $y;

This has the advantage of being more statically analyzable (both visually and 
for parsers).  It may also be more performant (in theory), as it could 
translate almost trivially to:

$f = new class extends \Closure implements TwoInts
{
public function __invoke(int $x, int $y): int
{
return $x + $y;
}
}

The downside is that it only works for user-defined closures that declare their 
support up-front, statically.  Something like strlen(...) or strtr(...) 
wouldn't work.  It's also a bit verbose, though using bindTo() directly on the 
closure is of similar length:

$f = (fn(int $x, int $y): int => $x + 

Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Aleksander Machniak

On 20.04.2023 16:15, Larry Garfield wrote:

or $point = clone($point, x: $x, y: $y, z: $z);

Also, I didn't see it mentioned, but maybe for future scope, these new
arguments should be passed to __clone().


They should not.  See 
https://peakd.com/hive-168588/@crell/object-properties-part-2-examples .  I 
went through and experimented with different syntaxes, and passing arguments to 
__clone() was by far the worst option in practice. :-)


Sure, I didn't propose it to be a solution for "clone with", but rather, 
if we implement "clone with" (no matter in which way) this is "cloning 
an object with extra properties", so therefore one would ask to have 
access to these extra properties inside __clone(). I'm not sure how 
useful that could be, but maybe worth mentioning in future scope or 
somewhere in the RFC.


And I think this syntax should still be on the table, no need for a new 
keyword.


$point = clone($point, x: $x, y: $y, z: $z);

--
Aleksander Machniak
Kolab Groupware Developer[https://kolab.org]
Roundcube Webmail Developer  [https://roundcube.net]

PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Tim Düsterhus

Hi

On 4/20/23 16:17, Larry Garfield wrote:

On Thu, Apr 20, 2023, at 7:00 AM, Rowan Tommins wrote:


Rather than making everything use an array or array-like syntax, I
would probably go the other way and scrap the special syntax for
dynamic names, making the whole thing look like a function call, with
support for array unpacking:

$point = clone $point with (x: $x, y: $y, z: $z);
$point = clone $point with (...['x' => $x, 'y' => $y, 'z' => $z]);


I agree here, for all the reasons Rowan indicated.  We already have a perfectly 
good syntax and semantics for named arguments that supports splat.  Using that 
here would handle all the use cases we care about, including dynamic names, 
without any additional syntax.



FWIW I'm not too attached to my array proposal [1]. I would also be fine 
with Rowan's proposal of making the "with()" syntactically identical to 
a function call, if that's more agreeable.


But please no entirely new syntax with braces as it currently is shown 
in the examples in the RFC.


Best regards
Tim Düsterhus

[1] Though I still consider arrays to be more "natural" than named 
arguments.


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Larry Garfield
On Thu, Apr 20, 2023, at 7:00 AM, Rowan Tommins wrote:

> Rather than making everything use an array or array-like syntax, I 
> would probably go the other way and scrap the special syntax for 
> dynamic names, making the whole thing look like a function call, with 
> support for array unpacking:
>
> $point = clone $point with (x: $x, y: $y, z: $z);
> $point = clone $point with (...['x' => $x, 'y' => $y, 'z' => $z]);

I agree here, for all the reasons Rowan indicated.  We already have a perfectly 
good syntax and semantics for named arguments that supports splat.  Using that 
here would handle all the use cases we care about, including dynamic names, 
without any additional syntax.  

Let's not shoe-horn arrays in here where they're not needed.  Making arrays 
themselves fancier/more compact with an alternate syntax is worth discussing, 
but that should be a separate RFC.

--Larry Garfield

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Larry Garfield
On Thu, Apr 20, 2023, at 7:22 AM, Aleksander Machniak wrote:
> On 20.04.2023 09:00, Rowan Tommins wrote:
>> Rather than making everything use an array or array-like syntax, I would 
>> probably go the other way and scrap the special syntax for dynamic names, 
>> making the whole thing look like a function call, with support for array 
>> unpacking:
>> 
>> $point = clone $point with (x: $x, y: $y, z: $z);
>> $point = clone $point with (...['x' => $x, 'y' => $y, 'z' => $z]);
>
> or $point = clone($point, x: $x, y: $y, z: $z);
>
> Also, I didn't see it mentioned, but maybe for future scope, these new 
> arguments should be passed to __clone().

They should not.  See 
https://peakd.com/hive-168588/@crell/object-properties-part-2-examples .  I 
went through and experimented with different syntaxes, and passing arguments to 
__clone() was by far the worst option in practice. :-)

--Larry Garfield

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Rowan Tommins

On 20/04/2023 08:11, Robert Landers wrote:

public function withStatus($code, $reasonPhrase = ''): Response
{
 return clone $this {
 $this->statusCode = $code;
  $this->reasonPhrase = "Old: $this->reasonPhrase, New: $reasonPhrase";
 }
};



Note that this is not the current proposed syntax. Since the keys are 
not dynamic, the current proposal is this:


public function withStatus($statusCode, $reasonPhrase = ''): Response {
  // perform validation here
  $reasonPhrase = "Old: $this->reasonPhrase, New: $reasonPhrase";
  return clone $this with {statusCode: $statusCode, reasonPhrase: 
$reasonPhrase};
}




public function withStatus($statusCode, $reasonPhrase = ''): Response {
   // perform validation here
   $reasonPhrase = "Old: $this->reasonPhrase, New: $reasonPhrase";
   return clone $this with compact('statusCode', 'reasonPhrase');
}



The compact() function always feels like a relic of the same era as 
create_function() and call_user_func(), both of which now have dedicated 
syntax.


That's what Nikita was talking about in the RFC section I quoted 
earlier: that compact('foo', 'bar') could be replaced with a dedicated 
syntax like [:$foo, :$bar]


So if we insisted on arrays, that would be:

public function withStatus($statusCode, $reasonPhrase = ''): Response {
  // perform validation here
  $reasonPhrase = "Old: $this->reasonPhrase, New: $reasonPhrase";
  return clone $this with [:$statusCode, :$reasonPhrase];
}

But I still don't see why an array should be the default case here, 
rather than using ... to unpack one if you really need to, like we do 
with arguments.


public function withStatus($statusCode, $reasonPhrase = null): Response {
  $newProps = [:$statusCode];
  if ( $reasonPhrase !== null ) {
  $newProps['reasonPhrase'] = "Old: $this->reasonPhrase, New: 
$reasonPhrase";
  }
  return clone $this with (...$newProps);
}


Regards,

--
Rowan Tommins
[IMSoP]

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Aleksander Machniak

On 20.04.2023 09:00, Rowan Tommins wrote:

Rather than making everything use an array or array-like syntax, I would 
probably go the other way and scrap the special syntax for dynamic names, 
making the whole thing look like a function call, with support for array 
unpacking:

$point = clone $point with (x: $x, y: $y, z: $z);
$point = clone $point with (...['x' => $x, 'y' => $y, 'z' => $z]);


or $point = clone($point, x: $x, y: $y, z: $z);

Also, I didn't see it mentioned, but maybe for future scope, these new 
arguments should be passed to __clone().


--
Aleksander Machniak
Kolab Groupware Developer[https://kolab.org]
Roundcube Webmail Developer  [https://roundcube.net]

PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Robert Landers
On Thu, Apr 20, 2023 at 9:01 AM Rowan Tommins  wrote:
>
> On 18 April 2023 17:48:00 BST, "Tim Düsterhus"  wrote:
> >I'd rather see only the fat-arrow being allowed. Unless I'm missing 
> >something, braces with colon is not used anywhere else, whereas braces + 
> >'=>' is known from match() and '=>' more generally is already used with 
> >array literals [1]. Having two similar syntaxes for the same thing is not 
> >great when both are commonly needed is not great. They need to be documented 
> >and learned by developers.
>
>
> I think it makes sense to have an unquoted form here, because the common case 
> is that they are names which analysers can match statically to particular 
> properties, not strings which will be analysed at runtime. There are plenty 
> of places in the language where dynamic names are allowed, but we don't just 
> use strings for the static case:
>
> ${'foo'} = 'bar'( constant('BAZ') )->{'quux'}();
>
>
> More specifically, the "name: $value" syntax matches named parameters, and 
> while you can use an array for that (via ... unpacking), we don't force users 
> to do so.
>
> In fact, the Future Scope of that RFC considered the opposite: using the 
> colon syntax in arrays, along with a shorthand for pulling the key name from 
> the variable name: 
> https://wiki.php.net/rfc/named_params#shorthand_syntax_for_matching_parameter_and_variable_name
>
> >  If I wanted to put these ideas into a general framework, I think one way 
> > to go about this would be as follows:
> >
> > - Consider identifier: $expr as a shorthand for "identifier" => $expr.
>  > - Consider :$variable as a shorthand for variable: $variable and thus 
> "variable" => $variable.
>
>
> That would give us:
>
> $point = ['x' => $x, 'y' => $y, 'z' => $z];
> $point = [x: $x, y: $y, z: $z];
> $point = [:$x, :$y, :$z];
>
> $point = new Point(...['x' => $x, 'y' => $y, 'z' => $z]);
> $point = new Point(x: $x, y: $y, z: $z);
> $point = new Point(:$x, :$y, :$z);
>
> $point = clone $point with {'x' => $x, 'y' => $y, 'z' => $z};
> $point = clone $point with {x: $x, y: $y, z: $z};
> $point = clone $point with {:$x, :$y, :$z};
>
>
>
> Rather than making everything use an array or array-like syntax, I would 
> probably go the other way and scrap the special syntax for dynamic names, 
> making the whole thing look like a function call, with support for array 
> unpacking:
>
> $point = clone $point with (x: $x, y: $y, z: $z);
> $point = clone $point with (...['x' => $x, 'y' => $y, 'z' => $z]);
>
>
>
> Regards,
>
> --
> Rowan Tommins
> [IMSoP]
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
>

I think using arrays here makes a lot of sense.

For example, this example:

public function withStatus($code, $reasonPhrase = ''): Response
{
return clone $this {
$this->statusCode = $code;
 $this->reasonPhrase = "Old: $this->reasonPhrase, New: $reasonPhrase";
}
};

could be rewritten with

public function withStatus($statusCode, $reasonPhrase = ''): Response {
  // perform validation here
  $reasonPhrase = "Old: $this->reasonPhrase, New: $reasonPhrase";
  return clone $this with compact('statusCode', 'reasonPhrase');
}

I, personally, would find this much more ergonomic than writing out
blocks of code and having a totally different syntax.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] [RFC] [Discussion] Clone with

2023-04-20 Thread Rowan Tommins
On 18 April 2023 17:48:00 BST, "Tim Düsterhus"  wrote:
>I'd rather see only the fat-arrow being allowed. Unless I'm missing something, 
>braces with colon is not used anywhere else, whereas braces + '=>' is known 
>from match() and '=>' more generally is already used with array literals [1]. 
>Having two similar syntaxes for the same thing is not great when both are 
>commonly needed is not great. They need to be documented and learned by 
>developers.


I think it makes sense to have an unquoted form here, because the common case 
is that they are names which analysers can match statically to particular 
properties, not strings which will be analysed at runtime. There are plenty of 
places in the language where dynamic names are allowed, but we don't just use 
strings for the static case:

${'foo'} = 'bar'( constant('BAZ') )->{'quux'}();


More specifically, the "name: $value" syntax matches named parameters, and 
while you can use an array for that (via ... unpacking), we don't force users 
to do so.

In fact, the Future Scope of that RFC considered the opposite: using the colon 
syntax in arrays, along with a shorthand for pulling the key name from the 
variable name: 
https://wiki.php.net/rfc/named_params#shorthand_syntax_for_matching_parameter_and_variable_name

>  If I wanted to put these ideas into a general framework, I think one way to 
> go about this would be as follows:
>
> - Consider identifier: $expr as a shorthand for "identifier" => $expr.
 > - Consider :$variable as a shorthand for variable: $variable and thus 
 > "variable" => $variable.


That would give us:

$point = ['x' => $x, 'y' => $y, 'z' => $z];
$point = [x: $x, y: $y, z: $z];
$point = [:$x, :$y, :$z];

$point = new Point(...['x' => $x, 'y' => $y, 'z' => $z]);
$point = new Point(x: $x, y: $y, z: $z);
$point = new Point(:$x, :$y, :$z);

$point = clone $point with {'x' => $x, 'y' => $y, 'z' => $z};
$point = clone $point with {x: $x, y: $y, z: $z};
$point = clone $point with {:$x, :$y, :$z};



Rather than making everything use an array or array-like syntax, I would 
probably go the other way and scrap the special syntax for dynamic names, 
making the whole thing look like a function call, with support for array 
unpacking:

$point = clone $point with (x: $x, y: $y, z: $z);
$point = clone $point with (...['x' => $x, 'y' => $y, 'z' => $z]);



Regards,

-- 
Rowan Tommins
[IMSoP]

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php