Hello Internals,
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Best,
Jakob
I love the idea!
The syntax for new objects looks a little verbose – would it be possible to
use
$foo = new Foo()[
property1 = "hello",
property2 = 5,
];
as an alternative to
$foo = (new Foo())->[
property1 = "hello",
property2 = 5,
];
Hello Internals,
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Best,
Jakob
For reference:
https://wiki.php.net/rfc/object-initializer
https://externals.io/message/106947
That was quite the discussion, and it started only 6 months ago.
-Mike
I love the idea!
The syntax for new objects looks a little verbose – would it be possible to
use$foo = new Foo()[
property1 = "hello",
property2 = 5,
];as an alternative to
$foo = (new Foo())->[
property1 = "hello",
property2 = 5,
];Hello Internals,
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Best,
Jakob
I love the idea!
Thanks Matthew!
The syntax for new objects looks a little verbose – would it be possible to use
$foo = new Foo()[
property1 = "hello",
property2 = 5,
];
The short answer: I don't think so.
This is not possible (though it's arguably 'nicer'):
new Foo()->doSomething(); // syntax error, unexpected '->'
Not sure why, but it's necessary to wrap the instantiation in brackets:
(new Foo())->doSomething(); // Ok
If someone can explain why, it would be enlightening!
Furthermore, dropping the arrow would create conflicting syntax with
array access:
$foo[1]; // Array access, give me the element with index 1, can be
used on objects as well
$foo[a = 1]; // Property assignment... ???
I'll update the RFC to reflect your proposal.
Thank you,
Jakob
I don't think it's conflicting if you insist on brackets after the new
expression. PHP Parser grammar for "new" expressions is
T_NEW
class_name_reference ctor_arguments
If you allowed the shorter syntax, ctor_arguments would allow an
object_properties entry.
I'm sure Nikita could clarify, though.
On Mon, Mar 16, 2020 at 9:10 AM Matthew Brown matthewmatthew@gmail.com
wrote:I love the idea!
Thanks Matthew!The syntax for new objects looks a little verbose – would it be possible
to use$foo = new Foo()[
property1 = "hello",
property2 = 5,
];
The short answer: I don't think so.This is not possible (though it's arguably 'nicer'):
new Foo()->doSomething(); // syntax error, unexpected '->'
Not sure why, but it's necessary to wrap the instantiation in brackets:
(new Foo())->doSomething(); // Ok
If someone can explain why, it would be enlightening!
Furthermore, dropping the arrow would create conflicting syntax with
array access:$foo[1]; // Array access, give me the element with index 1, can be
used on objects as well
$foo[a = 1]; // Property assignment... ???I'll update the RFC to reflect your proposal.
Thank you,
Jakob
I don't think it's conflicting if you insist on brackets after the new expression. PHP Parser grammar for "new" expressions is
T_NEW
class_name_reference ctor_argumentsIf you allowed the shorter syntax, ctor_arguments would allow an object_properties entry.
Sounds good, but this is unfamiliar territory to me, so would you mind
giving an example of an
alternative syntax you think would work?
I'm sure Nikita could clarify, though.
I know Nikita is busy but I hope he'll stop by this RFC one of these
days, as I have a few
things that needs his expertise :-)
Am 16.03.2020 um 12:48 schrieb Jakob Givoni jakob@givoni.dk:
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Sorry to say but the syntax look very un-PHP-like.
[] suggests an array but then is uses literals and = instead of 'strings'/$vars and => which looks wrong to my PHP eye.
Apart from that it feels weird that you have an object where you want to initialise so many properties manually instead of through some sort of constructor.
Is it more of a record than an object?
I'm not sure if I that is a pattern I would want to encourage.
My $.02,
- Chris
On Mon, Mar 16, 2020 at 9:26 AM Christian Schneider
cschneid@cschneid.com wrote:
[] suggests an array but then is uses literals and = instead of 'strings'/$vars and => which looks wrong to my PHP eye.
That's ok, square brackets are usually related to arrays and what I
want is something similar to array literals.
The rest of the syntax is exactly the same as when you assign
properties on a object currently.
you want to initialise so many properties manually instead of through some sort of constructor.
Yes, exactly. From the RFC:
"The purpose of this feature is to lighten the effort of populating
data structures, especially medium to large ones."
And as I say, since we don't have named parameters, this is a
pragmatic alternative.
Hey Jakob,
What happens if you have an expression that throws?
class Foo
{
public $a;
public $b;
public $c;
}
$instance = new Foo();
function iThrow() {
throw new \Exception();
}
try {
$foo ->[
a = 'a',
b = iThrow(),
c = 'c',
];
} catch (\Throwable $e) {
var_export($foo); // ???
}
What will the state of $foo
be here?
Marco Pivetta
Hello Internals,
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Best,
Jakob
What happens if you have an expression that throws?
class Foo
{
public $a;
public $b;
public $c;
}$instance = new Foo();
function iThrow() {
throw new \Exception();
}try {
$foo ->[
a = 'a',
b = iThrow(),
c = 'c',
];
} catch (\Throwable $e) {
var_export($foo); // ???
}
Hi Marco!
Trivial question - let's see what happens:
Just replace COPA with the old syntax and run it:
try {
$foo->a = 'a';
$foo->b = iThrow();
$foo->x = 'c';
} catch (\Throwable $e) {
var_export($foo); // ???
}
Result:
Foo::__set_state(array(
'a' => 'a',
'b' => NULL,
'c' => NULL,
))
So the first property will be set, the rest will be left as they were.
Best,
Jakob
Hey Jakob,
What happens if you have an expression that throws?
class Foo
{
public $a;
public $b;
public $c;
}$instance = new Foo();
function iThrow() {
throw new \Exception();
}try {
$foo ->[
a = 'a',
b = iThrow(),
c = 'c',
];
} catch (\Throwable $e) {
var_export($foo); // ???
}Hi Marco!
Trivial question - let's see what happens:Just replace COPA with the old syntax and run it:
try {
$foo->a = 'a';
$foo->b = iThrow();
$foo->x = 'c';
} catch (\Throwable $e) {
var_export($foo); // ???
}Result:
Foo::__set_state(array(
'a' => 'a',
'b' => NULL,
'c' => NULL,
))So the first property will be set, the rest will be left as they were.
That kinda makes the entire feature quite useless/uninteresting to me: if a
this does is desugaring into a set of assignments, then I'm not sure what
its advantage is.
I was kinda hoping for an atomic state mutation across multiple fields,
heh...
That kinda makes the entire feature quite useless/uninteresting to me: if a this does is desugaring into a set of assignments, then I'm not sure what its advantage is.
I'm sorry about that, but the motivation of the RFC is quite clear in
the "Motivation" section.
If those goals are not interesting to you then COPA won't help you.
I was kinda hoping for an atomic state mutation across multiple fields, heh...
Sounds like another RFC (that may make it in PHP 9.x+ ;-D)
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Hi Jakob,
Thanks for having another go at this feature, which I think a lot of people
would like in some form.
I think I agree with Matthew that the almost-but-not-quite array syntax
looks a bit odd, but that's something that can be worked on if we agree the
basic idea is sound.
I fear that the need to directly set public properties (or use __set) would
limit the use cases of this somewhat. I can see two ways we could arrive at
something more broadly useful:
- A syntax like this, plus better support for property setters (inline like
in C# etc, rather than needing __set) - Named parameters plus short-hand constructor definitions (e.g. function
__construct($this->foo) removing the need to type $this->foo = $foo)
Maybe we want to have all four, but that would leave us with a lot of
different ways of initialising objects, which might not be such a good
thing.
You mention using "argument bag" objects in place of named parameters, but
that feels like it would be even more awkward than using an associative
array. To get the example in the RFC doing something useful, you'd need
quite a lot of boilerplate:
class FooOptions {
string $mane;
string $hum;
}
class Foo {
string $mane;
string $hum;
public function __construct(FooOptions $options) {
// Still have to check for uninitialized properties, since COPA
won't enforce them
if ( ! isset($options->mane) || ! isset($options->hum) ) {
throw new Exception('Bad options');
}
// Still have to copy parameters one at a time
$this->mane = $options->mane;
$this-> hum = $options->hum;
}
}
$myFoo = new Foo((new FooOptions())->[
mane = 'get',
hum = 'life',
]);
Compare that to named parameters (picking a syntax that looks similar to
yours), even without any new constructor syntax:
class Foo {
string $mane;
string $hum;
public function __construct(string $mane, string $hum) {
// Type and mandatory param checks are automatic
// Still have to copy parameters one at a time, unless we
$this->mane = $mane;
$this-> hum = $hum;
}
}
$myFoo = new Foo([
mane = 'get',
hum = 'life',
]);
Writing that out also made me realise a key difference between this and
constructor short-hands, which is that COPA can only validate individual
parameters, not the whole object; so it's hard to complain if the user
forgets a mandatory field. Sometimes that wouldn't matter, but again, it
limits the cases where this syntax would be useful, even if we had property
setters.
Regards,
Rowan Tommins
[IMSoP]
Thanks for having another go at this feature, which I think a lot of people
would like in some form.
I agree!
Maybe we want to have all four, but that would leave us with a lot of
different ways of initialising objects, which might not be such a good
thing.
Remember that the RFC explicitly says it's not an object initializer,
nor does it solve "named parameters" which you mention.
If I had the choice, I'd go with named parameters when creating an
object, but I don't and
this is the next best thing. It's simple and it doesn't introduce any
new concepts.
It just allows you to do something inline that you could until now
only do line by line.
You mention using "argument bag" objects in place of named parameters, but
that feels like it would be even more awkward than using an associative
array.
The associative array is a common pattern, but it defeats name and
type checking.
An Options class add almost no extra boilerplate since you simply move
the properties from
the main class to the Options class.
Notice that in contrary to your example, I simply copy the Options
object whole into the main class.
COPA can only validate individual
parameters, not the whole object; so it's hard to complain if the user
forgets a mandatory field
Correct, the goals for COPA are quite clear in the "Motivation"
section and that kind of validation
is not one of them
Luckily you can still do things the way you prefer without COPA if it
doesn't suit you :-)
Best,
Jakob
Remember that the RFC explicitly says it's not an object initializer,
nor does it solve "named parameters" which you mention.
I wasn't expecting COPA to "solve" named parameters, just thinking that if
we already had named parameters, we might not bother with COPA.
That said, your "argument bag" example looks very much like it's trying to
solve named parameters to me.
An Options class add almost no extra boilerplate since you simply move
the properties from
the main class to the Options class.
Notice that in contrary to your example, I simply copy the Options
object whole into the main class.
Hm, I see, that does reduce the boilerplate somewhat, although it's still
split across two classes, and probably therefore two files, which is not
great.
You're missing some code in your example, though, because you've documented
one of the options as optional, implying the others should be mandatory; in
which case you need something like this in the constructor:
if ( ! isset($options->mane) || ! isset($options->hum) ) {
throw new Exception('Bad options');
}
Which would soon get longer with more complex options. Maybe it would be
refactored out into $options->isValid() or something, but it would have to
go somewhere, or you'll just get "uninitialized property" errors in random
parts of your application.
You'd also want to copy the property onto the real object if it was
initialising state rather than retaining a constant value; it would be a
bit weird to write $this->options->counter++ instead of $this->counter++
COPA can only validate individual
parameters, not the whole object; so it's hard to complain if the user
forgets a mandatory field
Correct, the goals for COPA are quite clear in the "Motivation"
section and that kind of validation
is not one of them
Luckily you can still do things the way you prefer without COPA if it
doesn't suit you :-)
It's not so much about preferring different styles, it's about what
circumstances this will be useful. It seems pretty rare that an object
would have no mandatory properties, so saying "if you have a mandatory
property, COPA is not for you" is ruling out a lot of uses.
It might be interesting to have the syntax run before the constructor
rather than after, or trigger some other magic method which could do
tidying and validation, so it could apply to more circumstances.
Regards,
Rowan Tommins
[IMSoP]
Hm, I see, that does reduce the boilerplate somewhat, although it's still
split across two classes, and probably therefore two files, which is not
great.
As an alternative to passive associative arrays, it's a small price to pay for
splitting concerns and having a documented signature for the data structure.
Named parameters is another alternative (unfortunately outside reach for now)
that would work great for a limited number of arguments. COPA does not
make named parameters obsolete, but works great for any number of arguments.
You're missing some code in your example, though, because you've documented
one of the options as optional, implying the others should be mandatory; in
which case you need something like this in the constructor:
I've rewritten parts of the RFC and some examples substantially,
and added your view of mandatory arguments in the "Open Issues" section.
It might be interesting to have the syntax run before the constructor
rather than after, or trigger some other magic method which could do
tidying and validation, so it could apply to more circumstances.
Hmmm, in the Write Once Properties RFC they seem to believe
that object construction is a "fuzzy" term and that lazy initialization
is a feature.
We can add better 'automagic' support for mandatory arguments,
filtering and validation
in incremental proposals, to avoid biting off more than we can chew, but we
have to start somewhere.
Let me know what you think!
Thank you for your feedback so far!
I'd be really curios to know what authors of the referenced RFCs think
about this, f.ex.
Michał (brzuchal@php.net), Andrey (andrewgrom@rambler.ru) and Nikita
(nikic@php.net)
Is simple inline object population useful in itself?
Is the proposed implementation as trivial as I think it is?
Best regards,
Jakob
Hi Jakob,
wt., 17 mar 2020 o 03:08 Jakob Givoni jakob@givoni.dk napisał(a):
Let me know what you think!
Thank you for your feedback so far!
I'd be really curios to know what authors of the referenced RFCs think
about this, f.ex.
Michał (brzuchal@php.net), Andrey (andrewgrom@rambler.ru) and Nikita
(nikic@php.net)Is simple inline object population useful in itself?
Is the proposed implementation as trivial as I think it is?
For object initializer, I was hoping to introduce a feature which with the
benefits of typed properties
could reduce the boilerplate on initializing object and setting their
properties in one expression.
This is somewhat what I personally do a lot for commands and events
construction where
those simply have public typed properties and I don't care if something
would be changed
in the meantime.
With current RFC for write-once, I can see object initializer could be an
awesome feature
with a combine of read-only properties.
Currently I would have to do a lot of:
$command = new SomeCommand();
$command->foo = 'bar';
$command->baz = true
$this->dispatch($command);
For SomeCommand looking like (with read-only for example purposes)
class SomeCommand {
public readonly string $foo;
public readonly bool $baz;
}
With object initializer, this could be reduced to:
$this->dispatch(new SomeCommand { foo = 'bar', baz = false });
But as mentioned IMO this is a different feature than what you propose and
personally I see no point
in reducing only assignment statements without combining it with object
construction for these kinds
of small objects, DTO's, commands, queries and events.
And agree with others that brackets look unnatural for anything related to
objects.
IIRC there were many questions about named parameters to pass for object
constructor or proper
property setters like in C#, but personally I don't need a constructor at
all in those cases since we
have typed properties and it looks like they could be also marked as
read-only in next major PHP version.
Cheers,
Michał Brzuchalski
Thank you, Michał, for chiming in :-)
On Tue, Mar 17, 2020 at 10:52 AM Michał Brzuchalski
michal.brzuchalski@gmail.com wrote:
For object initializer, I was hoping to introduce a feature which with the benefits of typed properties
could reduce the boilerplate on initializing object and setting their properties in one expression.
Exactly my motivation with COPA as well;
- Typed properties - check,
- Reduce boilerplate - check,
- Initializing and setting properties in one expression - check.
With object initializer, this could be reduced to:
$this->dispatch(new SomeCommand { foo = 'bar', baz = false });
And with COPA it would be;
$this->dispatch((new SomeCommand)->[ foo = 'bar', baz = false ]);
Subtle differences, when compared to the currently available alternative.
But as mentioned IMO this is a different feature than what you propose and personally I see no point
in reducing only assignment statements without combining it with object construction for these kinds
of small objects, DTO's, commands, queries and events.
Maybe you missed that COPA can be combined with object construction?
but personally I don't need a constructor at all in those cases since we
have typed properties and it looks like they could be also marked as read-only in next major PHP version.
Exactly, and as I couldn't help notice that you voted for the Write
Once Properties RFC, which clearly states that
object construction is a fuzzy term (meaning don't take it too
seriously) and lazy initialization is a feature...
I hope you will either reconsider your support for COPA or let me know
what I'm missing so I can
consider other directions.
śr., 18 mar 2020, 03:36 użytkownik Jakob Givoni jakob@givoni.dk napisał:
Thank you, Michał, for chiming in :-)
On Tue, Mar 17, 2020 at 10:52 AM Michał Brzuchalski
michal.brzuchalski@gmail.com wrote:For object initializer, I was hoping to introduce a feature which with
the benefits of typed properties
could reduce the boilerplate on initializing object and setting their
properties in one expression.
Exactly my motivation with COPA as well;
- Typed properties - check,
- Reduce boilerplate - check,
- Initializing and setting properties in one expression - check.
With object initializer, this could be reduced to:
$this->dispatch(new SomeCommand { foo = 'bar', baz = false });
And with COPA it would be;
$this->dispatch((new SomeCommand)->[ foo = 'bar', baz = false ]);
Subtle differences, when compared to the currently available alternative.
The difference is in object state initialization :
Using Object Initializer enforce that if a class is instantiated with the
Object Initializer, at the end of the instantiation and properties
initialization, all visible properties (depends on initialization scope) are
initialized, otherwise, a RuntimeException is thrown. This helps to avoid
bugs where a property is added to the class but forgot to be assigned it a
value in all cases where the class is instantiated and initialized.
https://wiki.php.net/rfc/object-initializer#restrictions
You can't do that with COPA cause it has nothing to do with object
instantiation.
But as mentioned IMO this is a different feature than what you propose
and personally I see no point
in reducing only assignment statements without combining it with object
construction for these kinds
of small objects, DTO's, commands, queries and events.
Maybe you missed that COPA can be combined with object construction?but personally I don't need a constructor at all in those cases since we
have typed properties and it looks like they could be also marked as
read-only in next major PHP version.
Exactly, and as I couldn't help notice that you voted for the Write
Once Properties RFC, which clearly states that
object construction is a fuzzy term (meaning don't take it too
seriously) and lazy initialization is a feature...I hope you will either reconsider your support for COPA or let me know
what I'm missing so I can
consider other directions.
On Wed, Mar 18, 2020 at 12:27 AM Michał Brzuchalski
michal.brzuchalski@gmail.com wrote:
Using Object Initializer enforce that if a class is instantiated with the Object Initializer,
at the end of the instantiation and properties initialization, all visible properties
(depends on initialization scope) are initialized, otherwise, a RuntimeException is thrown.
This helps to avoid bugs where a property is added to the class but forgot to be assigned
it a value in all cases where the class is instantiated and initialized. https://wiki.php.net/rfc/object-initializer#restrictions
Got it.
Out of curiosity, would you be able to summarize why you believe your
proposal was not accepted?
Thank you for your feedback so far!
I've rewritten parts of the RFC substantially
(https://wiki.php.net/rfc/compact-object-property-assignment)
so take another look if you too have ever wanted to
create, populate and send an object inside a function call.
Hi,
Jakob Givoni wrote:
Hello Internals,
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Best,
Jakob
I really don't like this (new Foo)->[]
syntax for a few reasons:
- It doesn't seem unreasonable to me to imagine that
->
could be
overloaded to take a value that isn't a string for the property name.
Of course, that would be(new Foo)->{[]}
not(new Foo)->[]
, but it
is too close for comfort for me. - It looks like short array syntax, but it's not an array, in fact
it is an object, which is a similar but different thing. - Brackets normally enclose an expression: if I have
new Foo
, I can
enclose that as(new Foo)
, yet this is adding something extra to the
new
expression while not being part of it? This is surprising to me.
If it affects thenew
, it should be inside the brackets. - Do we need the brackets?
I think there are some alternative syntaxes that could be used as
inspiration.
C99 has a versatile feature called “designated initialisers”, which lets
you initialise not only structs but arrays:
// Struct initialisation
struct some_struct foo = {
.bar = 1,
.baz = 2,
};
// Array initialisation
int bar[] = {
[0] = 1,
[1] = 2,
};
Notice how the syntax between the curly braces resemblance the normal
syntax for accessing and assigning to members of a type. A normal struct
member assignment on its own looks like foo.bar = 1;
, so within the
curly braces you write .bar = 1,
. Likewise a normal array member
assignment on its own looks like bar[0] = 1;
, so within the curly
braces you write [0] = 1,
. This resemblance provides familiarity for
someone seeing the syntax for the first time and provides a clue as to
what it does, and also retains the visual distinctiveness between array
and struct assignments.
If we were to copy the C99 model, perhaps it would look something like:
$foo = new Foo {
->bar = 1,
->baz = 2,
};
(I am assuming the call to the constructor has no arguments here, but
otherwise you would insert the normal argument list brackets before the
opening {
. I have used {
here like C, but perhaps [
would be
better. There would also be the question of whether =
should be =>
if we want to deliberately resemble arrays.)
However, we don't have to be so imaginitive and adapt a similar feature,
because C# already has the exact feature you are proposing, and calls it
“object initialisers”:
Foo foo = new Foo {
bar = 1,
baz = 2,
};
(I am again assuming the call to the constructor has no arguments. If
there are arguments, they go before the opening {
in C#.)
I am neutral on whether it's a better or worse syntax than using ->
.
Side-note, C# even supports a special kind of anonymous classes with
“object initialisers”:
var foo = new {
bar = 1,
baz = 2,
};
I think PHP won't need this given new stdClass {
would work perfectly
well, although it would be nice to have a shorter and prettier
alternative to the current (object)[
.
Anyway, thanks for proposing something I have wanted for ages but never
gotten round to implementing. :)
Andrea
However, we don't have to be so imaginitive and adapt a similar feature,
because C# already has the exact feature you are proposing, and calls it
“object initialisers”:Foo foo = new Foo {
bar = 1,
baz = 2,
};(I am again assuming the call to the constructor has no arguments. If
there are arguments, they go before the opening{
in C#.)I am neutral on whether it's a better or worse syntax than using
->
.Side-note, C# even supports a special kind of anonymous classes with
“object initialisers”:var foo = new {
bar = 1,
baz = 2,
};I think PHP won't need this given
new stdClass {
would work perfectly
well, although it would be nice to have a shorter and prettier
alternative to the current(object)[
.Anyway, thanks for proposing something I have wanted for ages but never
gotten round to implementing. :)
This is exactly what the recently declined Object Initializer RFC[1]
proposed. :)
[1] https://wiki.php.net/rfc/object-initializer
--
Christoph M. Becker
Hello Internals,
I'm opening up my new RFC for discussion:
https://wiki.php.net/rfc/compact-object-property-assignment
- A pragmatic approach to object literals
Let me know what you think!
Best,
Jakob
Hi again,
Since the two weeks mandatory discussion period is nearing its end and
there hasn't been any discussion activity for almost a week I'm
planning on opening the vote for COPA on Monday.
The RFC has been streamlined and updated to reflect all the discussion
points raised, - a couple of them are now in the Rejected Features
section and only the syntax question is still in the Open Issues
section. I haven't received any feedback on my proposed alternative
syntaxes so I guess we'll just put it to a vote (though I'm kind of
sad that nobody with solid internals experience has commented on the
implementability of any of them).
It's my first RFC, I know its a tough sell and I'm not expecting to be
successful the first time - I respect the vote 100%!
I really believe that COPA can help thousands of PHP developers
without introducing any new complexities to the language and my hope
is that perfect won't be the enemy of good this time around :-)
Any last words unsaid before the final lap commences?
Best regards,
Jakob
I really believe that COPA can help thousands of PHP developers
without introducing any new complexities to the language and my hope
is that perfect won't be the enemy of good this time around :-)Any last words unsaid before the final lap commences?
Hi Jakob,
It occurred to me thinking about alternative syntaxes that COPA could be
seen as a limited form of the "with" statement found in some languages,
e.g. JS [1], VisualBasic [2], Delphi [3], Groovy [4] (note that with
blocks in Python are something different).
[1]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
[2]
https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/with-end-with-statement
[3] http://www.delphibasics.co.uk/RTL.asp?Name=With
[4]
https://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Object.html#with(boolean,%20groovy.lang.Closure)
The Groovy version is particularly relevant, since it can be used as an
expression, not just a statement block, so this example from your RFC:
doTheFoo((new Foo)->[
a = 1,
b = 2,
c = 3,
]);
would I think look something like this in Groovy:
doTheFoo(new Foo().with(true) {
a = 1
b = 2
c = 3
})
The notable differences between COPA and with blocks are:
- with statements generally allow methods as well as properties to be
referenced on the given object - with blocks contain a series of full statements in some form (either a
syntactical block, or some form of closure); COPA instead contains a
special list of key-value pairs - with blocks generally fall back to other scopes if a property or
method is not found, and are often criticised for the resulting ambiguity
COPA trades the scope ambiguity for reduced flexibility: the left-hand
side of each assignment is always a property name, and the
right-hand-side is a normal expression, so doesn't benefit from the syntax.
This example from the RFC:
$myObj->[
foo = 10,
bar = $myObj->foo + 20,
];
Wouldn't need the extra reference to $myObj in Groovy:
myObj.with {
foo = 10
bar = foo + 20
}
Similarly, the nesting example in Future Scope:
$foo->[
a = 1,
b = $foo->b->[
c = 2,
],
];
Could be written something like this:
foo.with {
a = 1
b = b.tap {
c = 2
}
}
If we used a different sigil instead of $ for "property and method of
with'd object", we could avoid the ambiguity of most with blocks, but
get most of the power; something like this:
$foo = new Foo with {
->a = 1;
->b = ->1 + 10;
->c = new Bar with {
->d = 'Hello';
};
->start();
}
I'm not sure whether I like this idea or not, but I thought I'd share
it, because I think COPA as currently proposed is too narrow a use case
to deserve a special syntax.
Regards,
--
Rowan Tommins (né Collins)
[IMSoP]
Hi Rowan,
Rowan Tommins wrote:
Hi Jakob,
It occurred to me thinking about alternative syntaxes that COPA could be
seen as a limited form of the "with" statement found in some languages,
e.g. JS [1], VisualBasic [2], Delphi [3], Groovy [4] (note that with
blocks in Python are something different).
This is a good point, though thinking about it made me realise you can
already implement something like the proposed syntax in userland PHP:
(function ($obj) {
$foo = "foo";
$bar = "bar";
$baz = "baz";
$vars = `get_defined_vars()`;
foreach ($vars as $key => $value) {
$obj->$key = $value;
}
})($object);
It's not very elegant though, and unfortunately you can't move the
get_defined_vars()
part to another function. You could do:
use function \get_defined_vars as gdv;
function assign(object $object, callable $assigner) {
foreach ($assigner() as $key => $value) {
$object->$key = $value;
}
}
$object = new stdClass;
assign($object, function () {
$foo = "foo";
$bar = "bar";
$baz = "baz";
return gdv();
});
(By the way, if you want to reference existing properties, you can add
an extra foreach loop to import them into the current scope, or use
extract()
.)
But then I realise that you don't even need the closure, you can just
use a normal array, and it feels less necessary to add this to PHP core.
If using an assign() function is inconvenient, it could be part of an
object's constructor.
Hmm…
Thanks,
Andrea
Hi Rowan,
On Sun, Mar 29, 2020 at 10:00 AM Rowan Tommins rowan.collins@gmail.com
wrote:
myObj.with {
foo = 10
bar = foo + 20
}
I really like the suggested "with" syntax, - in fact I had
contemplated something like that along the way.
The slight "problem", I think, is that, as you mention, adding a fully
fledged "with" syntax would imply that you could do a lot more than simply
assigning values to properties.
And while I agree that that would be cool, it feels like overkill for the
actual problem COPA is trying to solve.
Do you think a narrow version of "with" syntax could be incorporated into
COPA without causing frustration? Or would it be wise to give it its own
RFC?
I haven't researched if there have already been RFCs proposing something
along the lines of "with", and as such I have no idea if it has already
been discussed and found problematic.
I'm not sure whether I like this idea or not, but I thought I'd share
it, because I think COPA as currently proposed is too narrow a use case
to deserve a special syntax.
I understand and fully agree that COPA is narrow, but I don't really
understand why that's a problem - I proposed it exactly because I felt that
its simplicity is its force.
Low hanging fruit is usually something I would encourage to go after.
I believe it's a trivial implementation that can help in uncountable
situations where you just need to assign values to a predefined data
structure in a single expression.
I'd really like to hear the arguments against such a cost-benefit
calculation.
Cheers,
Jakob
Hi Rowan,
On Sun, Mar 29, 2020 at 10:00 AM Rowan Tommins rowan.collins@gmail.com
wrote:myObj.with {
foo = 10
bar = foo + 20
}I really like the suggested "with" syntax, - in fact I had
contemplated something like that along the way.
The slight "problem", I think, is that, as you mention, adding a fully
fledged "with" syntax would imply that you could do a lot more than simply
assigning values to properties.
And while I agree that that would be cool, it feels like overkill for the
actual problem COPA is trying to solve.
Do you think a narrow version of "with" syntax could be incorporated into
COPA without causing frustration? Or would it be wise to give it its own
RFC?I haven't researched if there have already been RFCs proposing something
along the lines of "with", and as such I have no idea if it has already
been discussed and found problematic.I'm not sure whether I like this idea or not, but I thought I'd share
it, because I think COPA as currently proposed is too narrow a use case
to deserve a special syntax.I understand and fully agree that COPA is narrow, but I don't really
understand why that's a problem - I proposed it exactly because I felt that
its simplicity is its force.
Low hanging fruit is usually something I would encourage to go after.
I believe it's a trivial implementation that can help in uncountable
situations where you just need to assign values to a predefined data
structure in a single expression.I'd really like to hear the arguments against such a cost-benefit
calculation.Cheers,
Jakob
I went into this in my earlier Object Ergonomics post, but in short, it's quite possible for a solution to be too narrow. It becomes useful in too small a subset of cases to justify its added conceptual weight (and contrary to the view of some, "if you don't like it don't use it" is just not a thing when you're working in OSS, because someone is going to use it and you'll inherit that code). A narrow solution can also inadvertently serve to make something too easy that should be done rarely.
In this case, the problem with COPA as proposed is that it only works for public properties that are assigned literally. That is, in my experience, a very rare case. Most objects want and need to have private or protected properties for a large variety of reasons, which would make COPA useless. Or it would nudge users toward making more properties public so that they can use the shorter COPA, but in the absence of a readonly keyword, asymmetric visibility, property accessor, or something along those lines that is a net loss as it means more properties become public when they really shouldn't be.
In that case, a broader, less narrow solution is superior: Look at the actual pain points generally, find out their common elements, and address those. IMO, the combination of constructor promotion and named parameters would have a much better ROI than a dedicated syntax, it would not suffer from the public properties problem, it would still allow for constructor parameters that are not a 1:1 map to properties, but it would still serve to greatly reduce the amount of typing needed. That is, that approach offers more functionality for roughly the same or less additional syntax.
In short, the cost-benefit calculation says this is useful in too narrow a case, and has the potential to encourage bad design in the name of typing less. Other alternatives address that problem space better.
--Larry Garfield
Hi Larry!
I have a lot of respect for the vision you're working on. Great
advances sometimes come out of great visions.
But let's not forget that so far it's only a vision and not all
visions come to fruition. We don't know - at the moment it's anybody's
guess.
At this point I hardly expect you to change your mind, and it's not
that you don't have all the right in the world to an opinion, but it
seems to me sometimes it's easy to confuse opinion with fact. Hence,
for the sake of my own sanity I will reply to some of your comments.
If you feel we're going around in circles, feel free to ignore my
replies. For sure, no need to double down on our disagreements.
I went into this in my earlier Object Ergonomics post, but in short, it's quite possible for a solution to be too narrow.
It becomes useful in too small a subset of cases to justify its added conceptual weight.
And what exactly is the added conceptual weight of COPA?
As I've stated time and time again, COPA doesn't introduce any new
concepts or complexities.
If you disagree with that, I'd really like to hear exactly what those
new concepts or complexities amount to in your eyes.
(and contrary to the view of some, "if you don't like it don't use it" is just not a thing when you're working in OSS,
because someone is going to use it and you'll inherit that code).
Yeah, God forbid such madness! :-D
A narrow solution can also inadvertently serve to make something too easy that should be done rarely.
[...]
Or it would nudge users toward making more properties public so that they can use the shorter COPA,
but in the absence of a readonly keyword, asymmetric visibility, property accessor, or something along those
lines that is a net loss as it means more properties become public when they really shouldn't be.
Hmmm, record-like data structures (structs?) - devils work in
disguise, for sure... :-D
If I were being arrogant I'd say that maybe allowing a trailing comma
in parameter lists will nudge users towards making methods take more
parameters than they should.
But I think it's prudent to be modest in such judgements.
In this case, the problem with COPA as proposed is that it only works for public properties that are assigned literally. That is, in my experience, a very rare case.
You see, here's the funny thing: It's rare because it's a hassle!
MY experience (I do have some 20 years' worth of such...) is that
people tend to use associative arrays instead for data records and the
like. COPA aims to change that.
And it's not true that the values have to be assigned literally - the
assignment can invoke a magic method which can validate and modify a
value and store it privately. I'm not saying that is good or bad, just
that it's possible and that fact should not be misrepresented.
Most objects want and need to have private or protected properties for a large variety of reasons, which would make COPA useless.
Ooops, careful with such an overstatement, buddy! I don't want to
enter into this debate here, just point out that this generalisation
is overreaching.
Again, some modesty would suit the debate better. Truth before emotions also.
IMO, the combination of constructor promotion and named parameters would have a much better ROI than a dedicated syntax,
it would not suffer from the public properties problem, it would still allow for constructor parameters that are not a 1:1 map to properties,
but it would still serve to greatly reduce the amount of typing needed. That is, that approach offers more functionality for roughly the
same or less additional syntax.
The proposed syntaxes for those features are much more complex than
COPA's - I hope I'm not misrepresenting it when I say that it
currently involves 2 new ways to write a constructor, moving
declaration of properties inside a method signature and a whole new
annotation/attribute paradigm to try to make sense of it all. And
we're nowhere near consensus on any of it.
The revived constructor promotion syntax cannot replace COPA without
named parameters - and named parameters has not made any significant
progress since 2013, unfortunately.
I would love to see some form of named parameters, but I'm not holding
my breath for it.
In short, the cost-benefit calculation says this is useful in too narrow a case, and has the potential to encourage bad design in the name of typing less.
Other alternatives address that problem space better.
To list the facts:
- Is COPA narrow? Yes
- Is COPA useless? No
- Will COPA encourage bad design and be overused? Opinion!
- Will COPA encourage struct-like objects over anonymous array? Yes
- Will COPA pollute the symbol/syntax space? Very little
- Does COPA have any implications for future language evolution? None
that have been shown so far - Is COPA trivial to implement? Yes
Larry, keep up the great work with the Object Ergonomics. I'll follow
it closely and contribute when I can.
I will move forward with the vote soon. Based on the latest feedback
I'd be surprised if it got even a single yes-vote, but I want to have
this experience under my belt as well.
I respect the vote. I will learn from it in any case. I only wish the
vote isn't skewed by opinionated misrepresentation of COPA.
Thanks for reading and code responsibly!
Jakob
Hi,
In this case, the problem with COPA as proposed is that it only works for
public properties that are assigned literally. That is, in my experience,
a very rare case.You see, here's the funny thing: It's rare because it's a hassle!
MY experience (I do have some 20 years' worth of such...) is that
people tend to use associative arrays instead for data records and the
like. COPA aims to change that.
COPA won't make me use objects instead of arrays (in legacy code). The
problem is writing a ton of classes for all array ever used, in a legacy
file structure that comes with more maintenance than you'd like. What would
make me use objects would probably be run-time interfaces via a return
contract in combination with anonymous classes.
Most objects want and need to have private or protected properties for a
large variety of reasons, which would make COPA useless.Ooops, careful with such an overstatement, buddy! I don't want to
enter into this debate here, just point out that this generalisation
is overreaching.
I don't remember when the last time was that I used public properties, even
for the simplest data objects. Constructors provide all the features I need
at this point, and I'm much more interested in making constructors have
less boilerplate than their initialization. I can imagine COPA being more
attractive once (public) read only properties are a thing, though even then
I'd much rather use a constructor as it will let me find usages and
add/swap arguments easily. Finding COPA usages of a class will be a lot
harder compared to constructors.
Regards,
Lynn
Hi Jakob,
- Will COPA pollute the symbol/syntax space? Very little
- Does COPA have any implications for future language evolution? None
that have been shown so far
COPA is by its nature a brand new syntax, and that does have implications
for the language; that syntax has to be supported by whatever parser we
might adopt, and in conjunction with any future syntax we add.
Even if it doesn't cause ambiguity to the parser, there are only so many
special syntaxes that users of the language will want to learn. As an
extreme example, imagine if we had named parameters using braces and equal
signs, object initialisers, and COPA:
$foo = new Bar({someParam = $someValue}) { someProperty = $initialValue }
-> [ someProperty = $newValue ];
I hope you agree that this combination would be horribly confusing, with
each part looking similar but having different semantics. It follows that
whichever of those syntaxes we accept first makes the others less likely,
and that is a real cost.
It is true that we sometimes risk making the perfect the enemy of the good,
but there is also a risk in adopting something hastily and then finding it
hard to improve later without breaking things.
Regards,
Rowan Tommins
[IMSoP]
Hi Jakob,
I understand and fully agree that COPA is narrow, but I don't really
understand why that's a problem - I proposed it exactly because I felt that
its simplicity is its force.
Low hanging fruit is usually something I would encourage to go after.
I believe it's a trivial implementation that can help in uncountable
situations where you just need to assign values to a predefined data
structure in a single expression.I'd really like to hear the arguments against such a cost-benefit
calculation.
Others have said it reasonably well, but for me, a key point is that it
reserves a new syntax, with no obvious way of reusing that syntax for any
wider features in future. That weighs in on the cost side of the
calculation, because syntax is a somewhat limited resource - we often have
problems adding things without ambiguity in the parser, and a language with
lots of single-purpose syntaxes is going to be harder to learn than one
with a few multi-purpose syntaxes.
If COPA was the first part of a wider set of complementary features,
building on it as a base, it would be more attractive to me; but as
currently proposed, it feels like any new feature in this area (object
initialisers, named arguments, etc) would have to compete with it instead.
Regards,
Rowan Tommins
[IMSoP]
The current RFC text is not consistent with using =
or =>
for the
assignments. The assignment =
is vastly more common, though, and I
don't understand why if we are using an array syntax we aren't using
=>
.
I intend to vote no. I agree that we need to do something in this
space, but this is not it. I don't want perfect to be the enemy of
good either, but I think this still needs more work. Maybe interested
parties (including me) can collaborate off-list somewhere?
On Mon, Mar 30, 2020 at 3:06 AM Levi Morrison via internals <
internals@lists.php.net> wrote:
The current RFC text is not consistent with using
=
or=>
for the
assignments. The assignment=
is vastly more common, though, and I
don't understand why if we are using an array syntax we aren't using
=>
.I intend to vote no. I agree that we need to do something in this
space, but this is not it. I don't want perfect to be the enemy of
good either, but I think this still needs more work. Maybe interested
parties (including me) can collaborate off-list somewhere?
This is going to be a "no" vote from me as well. I don't think it's really
possible to "save" this proposal though, at least if it remains as syntax
sugar to perform multiple property assignments to one object. The syntax
has two use-cases: Object initialization and everything else. I don't think
it's doing a good job at solving object initialization, for reasons that
others (and in particular Larry) have already pointed out in detail. And I
don't really see a use case for this kind of mass property assignment
outside of object initialization.
I don't have much of a problem with the limitation to public properties (I
love public properties!), the problem why this is unsuitable for object
initialization is more that it is in practice mutually exclusive with
constructors. You can either have a class that defines a constructor, in
which case you can't use COPA with it (as the constructor would be doing
the initialization -- ignore the argument-less case here), or you have a
class without a constructor, in which case you essentially must use COPA.
There's also no migration pathway between these two modes, in that
switching between them is a backwards compatibility break for library
consumers. And if it turns out that an object needs non-trivial
construction logic at a later time, you can not easily introduce it without
breaking COPA style initialization.
This problem is not really limited to this proposal, it's a general problem
of retrofitting object initialization syntax to a language that
traditionally did not have it, and it doesn't seem to be an easy problem to
solve.
Regards,
Nikita
Hi Levi,
On Sun, Mar 29, 2020 at 8:06 PM Levi Morrison
levi.morrison@datadoghq.com wrHote:
The current RFC text is not consistent with using
=
or=>
for the
assignments. The assignment=
is vastly more common, though, and I
don't understand why if we are using an array syntax we aren't using
=>
.
Not sure what you mean - are you saying the RFC has internal inconsistencies?
Heads up!
I've moved the RFC to the voting phase:
https://wiki.php.net/rfc/compact-object-property-assignment
Voting is open until the end of Monday, April 13 (your time zone :-).
Good night,
Jakob
I've moved the RFC to the voting phase:
Hi Jakob,
Please don't forget to send a separate email announcing the voting
with a separate subject.
"Send an email to internals@lists.php.net announcing the start of
voting for your RFC. Start a new mail thread and put “[VOTE]” in the
subject. Set a deadline for voting; the minimum period is two weeks."
That avoids people not seeing the vote announcement.
cheers
Dan
Ack
Hi Dan,
Please don't forget to send a separate email announcing the voting
with a separate subject.
This email already has a separate subject, right? Something else missing?
Heads up!
I've moved the RFC to the voting phase:
https://wiki.php.net/rfc/compact-object-property-assignment
Voting is open until the end of Monday, April 13 (your time zone :-).
Good night,
Jakob
Hi guys,
I don't understand why people are replying to this email that has
[VOTE] in the subject as if it's still the discussion thread.
Please let me know if there's something I can do about it.
Best,
Jakob
I don't understand why people are replying to this email that has
[VOTE] in the subject as if it's still the discussion thread.
Please let me know if there's something I can do about it.
The problem Jakob is that the new message is STILL attached to the
original discussion. If you simply changed the 'DISCUSSION' to 'VOTE'
then your email client still had the original message links in place.
The whole email process is something of a mess, and the 'correct' way
would have been to start with an empty message, add a clean subject and
text and then send it to the list ...
--
Lester Caine - G8HFL
Contact - https://lsces.uk/wiki/Contact
L.S.Caine Electronic Services - https://lsces.uk
Model Engineers Digital Workshop - https://medw.uk
Rainbow Digital Media - https://rainbowdigitalmedia.uk
The problem Jakob is that the new message is STILL attached to the
original discussion. If you simply changed the 'DISCUSSION' to 'VOTE'
then your email client still had the original message links in place.
The whole email process is something of a mess, and the 'correct' way
would have been to start with an empty message, add a clean subject and
text and then send it to the list ...
ok, terribly sorry, will do that now then
thanks lester