Hi internals,
In every place where key
is a valid php identifier
(e.g. can be used in PHP 8.0's named parameters),
I propose to allow [key: expr]
to be used instead of ['key' => expr]
.
(including list(key: expr)
and array(key: expr)
(This can be mixed anywhere with existing key/value syntax such as $key => $value
, ...
, etc)
The implementation can be found inathttps://github.com/php/php-src/pull/6635
This is useful for shortening long arrays where the keys are known literals,
e.g.
return [success: true, data: $data, cursor: $cursor];
// is equivalent to the following, but shorter:
return ['success' => true, 'data' => $data, 'cursor' => $cursor];
This uses a similar syntax to named parameter invocations,
making it unlikely to cause future parser conflicts.
// Invoking a function with PHP 8.0 named parameters.
process_api_result(success: true, data: $data);
This can also be used in the older array()
value and list()
destructuring
syntaxes. Forbidding key: value
there seemed like
an unnecessary restriction that would make
the language harder to remember and a language specification a bit longer.
I haven't written up an RFC yet, but an older RFC for PHP 5 https://wiki.php.net/rfc/bare_name_array_literal
includes most of the arguments I plan to make, as well as the PR description. Things that have changed since then include:
- In php 8.0, named parameters were already added, so the
key: expr
syntax is not likely to cause conflicts with future syntax. - Users would already be familiar with this syntax and its meaning due to named parameters.
- There are better open source static analyzers to detect misuse of array keys or passing unexpected types to arrays (Phan, Psalm, PHPStan) when code is properly annotated
https://wiki.php.net/rfc/named_params#shorthand_syntax_for_matching_parameter_and_variable_name mentioned this among the future scope.
It had also suggested :$var
or =$var
as shorthand for var: $var
, but this is going to be left out of this feedback - https://externals.io/message/101698 has mostly negative feedback on a recent proposal (and there are multiple syntax candidates)
Any feedback?
Thanks,
Tyson
In every place where
key
is a valid php identifier
(e.g. can be used in PHP 8.0's named parameters),
I propose to allow[key: expr]
to be used instead of['key' => expr]
.
This is an immediate "no" from me: it multiplies the ways to write the
same thing from 2 to 4, in order to save a few bytes, in a few instances.
I think this is something that Douglas Crockford got absolutely right
when he simplified JavaScript object syntax to formalise JSON: every
valid key can be represented as a quoted string, so if the quotes are
always there, you don't need to remember a list of rules about reserved
words, allowed characters, etc.
This is useful for shortening long arrays where the keys are known literals,
e.g.return [success: true, data: $data, cursor: $cursor]; // is equivalent to the following, but shorter: return ['success' => true, 'data' => $data, 'cursor' => $cursor];
Although common, this is not a good use of arrays; if your keys are
"known literals", they should be fields of some object:
return new Result(success: true, data: $data, cursor: $cursor);
If you don't want to declare a class (yet), you can use an anonymous
object. Rather than yet another way to write arrays, it would be great
to have some more powerful syntax for those; currently you'd have
something like this:
return new class(success: true, data: $data, cursor: $cursor) { public
function __construct(public bool $success, public array $data, public
CursorInterface $cursor) {} };
Brainstorming, we could perhaps extend property promotion into the "new
class" clause, so that you could write this:
return new class(public bool success: true, public array data: $data,
public CursorInterface cursor: $cursor) {};
Regards,
--
Rowan Tommins
[IMSoP]
Hi Tyson,
Interesting proposal, but I was going to reply with the exact same response
as Rowan. Why not use DTOs for such purposes? Arrays have rather limited
use cases. If you need to return multiple results from a function then you
should return an object with typed properties.
I don't like the idea of adding yet another syntax for creating arrays,
especially one with limitations. If your array keys contain spaces or
special characters then you need to use the current syntax. Adding to the
fact that arrays have no type safety, I don't see much use for the newly
proposed syntax.
I would prefer an improved syntax for creation of anonymous objects. This
is something which I have been annoyed with, myself.I'd like to see a
simple way of creating anonymous objects with typed properties.
Regards,
Kamil
I would prefer an improved syntax for creation of anonymous objects. This
is something which I have been annoyed with, myself.I'd like to see a
simple way of creating anonymous objects with typed properties.
Another advantage arrays currently have over anonymous objects is destructuring -
if this was (somehow?) also made possible with objects, this would be the
best of both worlds.
(Returning multiple named values from a function is also mentioned in the
use-cases of the RFC.)
I know this works:
[ "foo" => $foo, "baz" => $baz ] = (array) $object;
(Alternatively also using get_object_vars()
instead of casting.)
But we both have to convert to an intermediate array again, and lose the
type information of the object (eg. for static analysis), so this could also be
made more ergonomic if we want to go down the anonymous object route.
Hi Mel Dafert,
I would prefer an improved syntax for creation of anonymous objects. This
is something which I have been annoyed with, myself.I'd like to see a
simple way of creating anonymous objects with typed properties.Another advantage arrays currently have over anonymous objects is destructuring -
if this was (somehow?) also made possible with objects, this would be the
best of both worlds.
(Returning multiple named values from a function is also mentioned in the
use-cases of the RFC.)
I know this works:[ "foo" => $foo, "baz" => $baz ] = (array) $object;
(Alternatively also using
get_object_vars()
instead of casting.)
But we both have to convert to an intermediate array again, and lose the
type information of the object (eg. for static analysis), so this could also be
made more ergonomic if we want to go down the anonymous object route.
Ideas for syntax:
There's object(foo: $foo, 'hyphenated-key' => $key) = $object;
,
but that would require making object
a reserved keyword, and it currently isn't one.
- I had suggested using
$x = object(foo: $foo);
as a shorthand for$x = (object)['foo' => $foo];
at some point. Feedback was negative for largely unrelated reasons (stdClass in general). https://externals.io/message/112082
object{foo: $foo}
seems similarly unambiguous by requiring at least one property but would also require a new reserved word
Or ({ foo: $foo }) = $object;
- On second thought, I'm against that - that is very likely to conflict with possible future proposals to parse block expressions.
(e.g. getting parsed as a label for a goto followed by returning the variable $foo.)
list{ foo: $foo } = $object
is unambiguous, I guess
(object)['foo' => $foo] = $object;
was another idea but that wouldn't even work.
It's already valid syntax that is parsed as casting an assignment to an object.
->{'foo' => $foo} = $object;
may be parseable but doesn't make much sense.
Thanks,
Tyson
Hi Tyson,
Regardless of what others are saying, I personally like your RFC. The
reasons are following:
- Because named keys are valid PHP names, they are resolved at the
parsing level. Thus, they will not be validated again and again.
Later, no constants are looked for. - At the runtime level, no variable location is done.
- Since all keys are to be passed as PHP names, both single quoted
and double quoted strings are not dealt with. Similarly, concatenation
operations are not also performed. - Because all named keys are strings by default, type checking is not enforced.
-
key=>value
costs 2 characters without spaces. If a keyboard user,
like me, wants to make its code easier to navigate, it adds two spaces
to either sides of=>
operator. Then, single quoted and double
quoted strings have two extra characters, too! Quotes + spaces +
assignment =:
2 + 2 + 2 = 6. If your suggested feature is added, 6 will be reduced
to two — a colon follows a name, and a space follows the colon. Yay,
we have formulated a solution that simplifies keyboard navigation.
Yes, the idea of making anonymous classes/objects easier seams also
compelling. Since this is a discussion, I propose to use braces {}
instead of brackets []
and follow the pattern that Tyson is
suggesting. I know that it is similar to JavaScript, but it will be
different in one regard that it disallows the use of strings
(regardless of whether they are literals).
Because it requires extra effort to perform array-related operations
on objects, I prefer these two syntaxes:
-
array(a: 1, b: 2, c: 3);
-
[a: 1, b: 2, c: 3];
Best Regard
Hamza Ahmad
Hi Mel Dafert,
I would prefer an improved syntax for creation of anonymous objects. This
is something which I have been annoyed with, myself.I'd like to see a
simple way of creating anonymous objects with typed properties.Another advantage arrays currently have over anonymous objects is
destructuring -
if this was (somehow?) also made possible with objects, this would be the
best of both worlds.
(Returning multiple named values from a function is also mentioned in the
use-cases of the RFC.)
I know this works:[ "foo" => $foo, "baz" => $baz ] = (array) $object;
(Alternatively also using
get_object_vars()
instead of casting.)
But we both have to convert to an intermediate array again, and lose the
type information of the object (eg. for static analysis), so this could
also be
made more ergonomic if we want to go down the anonymous object route.Ideas for syntax:
There's
object(foo: $foo, 'hyphenated-key' => $key) = $object;
,
but that would require makingobject
a reserved keyword, and it currently
isn't one.
- I had suggested using
$x = object(foo: $foo);
as a shorthand for$x = (object)['foo' => $foo];
at some point. Feedback was negative for largely unrelated reasons
(stdClass in general). https://externals.io/message/112082
object{foo: $foo}
seems similarly unambiguous by requiring at least one
property but would also require a new reserved wordOr
({ foo: $foo }) = $object;
- On second thought, I'm against that - that
is very likely to conflict with possible future proposals to parse block
expressions.
(e.g. getting parsed as a label for a goto followed by returning the
variable $foo.)
list{ foo: $foo } = $object
is unambiguous, I guess
(object)['foo' => $foo] = $object;
was another idea but that wouldn't even
work.
It's already valid syntax that is parsed as casting an assignment to an
object.
->{'foo' => $foo} = $object;
may be parseable but doesn't make much sense.Thanks,
Tyson--
To unsubscribe, visit: https://www.php.net/unsub.php
In every place where
key
is a valid php identifier
(e.g. can be used in PHP 8.0's named parameters),
I propose to allow[key: expr]
to be used instead of['key' => expr]
.This is an immediate "no" from me: it multiplies the ways to write the
same thing from 2 to 4, in order to save a few bytes, in a few instances.I think this is something that Douglas Crockford got absolutely right
when he simplified JavaScript object syntax to formalise JSON: every
valid key can be represented as a quoted string, so if the quotes are
always there, you don't need to remember a list of rules about reserved
words, allowed characters, etc.This is useful for shortening long arrays where the keys are known literals,
e.g.return [success: true, data: $data, cursor: $cursor]; // is equivalent to the following, but shorter: return ['success' => true, 'data' => $data, 'cursor' => $cursor];
Although common, this is not a good use of arrays; if your keys are
"known literals", they should be fields of some object:return new Result(success: true, data: $data, cursor: $cursor);
If you don't want to declare a class (yet), you can use an anonymous
object. Rather than yet another way to write arrays, it would be great
to have some more powerful syntax for those; currently you'd have
something like this:return new class(success: true, data: $data, cursor: $cursor) { public
function __construct(public bool $success, public array $data, public
CursorInterface $cursor) {} };Brainstorming, we could perhaps extend property promotion into the "new
class" clause, so that you could write this:return new class(public bool success: true, public array data: $data,
public CursorInterface cursor: $cursor) {};
I agree with Rowan on both points. The issues this would supposedly solve a better solved by continuing to work on making real-objects easier to work with. PHP 8 made huge strides in that area. Making anon classes easier would be the next logical step.
Also, Mel mentioned destructuring. Ilija has partially-finished work on a pattern matching operator that would include destructuring here:
https://wiki.php.net/rfc/pattern-matching
It's not going to make it into 8.1, sadly, but hopefully that or something like it would make it into 8.2.
--Larry Garfield
Hi Rowan Tommins,
This is an immediate "no" from me: it multiplies the ways to write the
same thing from 2 to 4, in order to save a few bytes, in a few instances.
It's from 4 to 6 if you include single quoted strings vs double quoted strings.
If linters and automatic fixers were set up for a project to enforce it (e.g. phpcbf),
there would only be one way that literals would be used in a project either way.
I think this is something that Douglas Crockford got absolutely right
when he simplified JavaScript object syntax to formalise JSON: every
valid key can be represented as a quoted string, so if the quotes are
always there, you don't need to remember a list of rules about reserved
words, allowed characters, etc.
It's the same rules as parts of identifiers or variable names.
I don't think there are any reserved words for named params or this proposal(default
, etc. are allowed despite being keywords)
What makes sense for a serialization format which will have hundreds of encoders/decoders may not make sense for a programming language.
This is useful for shortening long arrays where the keys are known literals,
e.g.return [success: true, data: $data, cursor: $cursor];
// is equivalent to the following, but shorter:
return ['success' => true, 'data' => $data, 'cursor' => $cursor];Although common, this is not a good use of arrays; if your keys are
"known literals", they should be fields of some object:return new Result(success: true, data: $data, cursor: $cursor);
If there is only a single place where an array with those keys is returned,
or if dynamic properties get added later,
then creating a class may not be worth it.
Developers would have to switch between files and keep track of which classes were
ordinary data storage and which had side effects or transformed parameters.
If you don't want to declare a class (yet), you can use an anonymous
object. Rather than yet another way to write arrays, it would be great
to have some more powerful syntax for those; currently you'd have
something like this:return new class(success: true, data: $data, cursor: $cursor) { public
function __construct(public bool $success, public array $data, public
CursorInterface $cursor) {} };
Brainstorming, we could perhaps extend property promotion into the "new
class" clause, so that you could write this:return new class(public bool success: true, public array data: $data,
public CursorInterface cursor: $cursor) {};
I think anonymous objects would benefit some use cases but would not be useful for every use case (e.g. short single-file shell scripts),
though the typed properties and constructor property promotion are definitely convenient.
Also, with no common interface between those anonymous classes,
using just anonymous classes would be writing functions that accept any object or return any object,
which would be error prone and hard to analyze.
Those anonymous classes wouldn't have any ancestors in common for instanceof
checks.
Also, if there were optional parameters, that can be represented in non-standard JSDoc supported by static analyzers
(success: bool, data?: array, cursor?: CursorInterface, errorMessage?: string
),
but that wouldn't be represented in an anonymous class
(setting a property to null is not the same as omitting it).
This reminds me of https://docs.python.org/3/library/collections.html#collections.namedtuple
but I don't plan to propose that (something similar could be done by validating all property names are valid,
getting the sha1 of the ordered list of property names to choose a class name,
and optionally setting properties to readonly
(if the RFC passes) and forbidding dynamic properties,
and extending some common interface).
Thanks,
Tyson
If linters and automatic fixers were set up for a project to enforce it (e.g. phpcbf),
there would only be one way that literals would be used in a project either way.
The only way to be consistent across a whole project would be to never
use the new syntax, since it can't be used for all arrays/keys.
Otherwise, you'd have to encourage either mixing the styles within one
array, or switching arrays back and forth if keys are added and removed
which meet/don't meet the requirements.
Also, with no common interface between those anonymous classes,
using just anonymous classes would be writing functions that accept any object or return any object,
which would be error prone and hard to analyze.Those anonymous classes wouldn't have any ancestors in common for
instanceof
checks.
Yes, the simplest anonymous class, with only untyped public properties,
is equivalent to stdClass, which in turn is equivalent to a plain array.
However, you can then add natively checked types, implement interfaces,
extend base classes, use traits, and so on. And then you can switch to a
named class with minimal impact on surrounding code.
Unfortunately, a neat syntax like object(foo: $foo) loses most of those
advantages; what we need is something that lets us keep the class body,
but "capture" variables from the declaring scope somehow, e.g.
$foo = new class(public bool success: true, public array data: $data,
public CursorInterface cursor: $cursor) implements Bar { use BarTrait; }
or
$foo = new class implements Bar { public bool $success = true; public
array $data = lexical $data; public CursorInterface $cursor = lexical
$cursor; use BarTrait; }
Also, if there were optional parameters, that can be represented in non-standard JSDoc supported by static analyzers
(success: bool, data?: array, cursor?: CursorInterface, errorMessage?: string
),
but that wouldn't be represented in an anonymous class
(setting a property to null is not the same as omitting it).
Certainly, if what you have are dynamic keys rather than fixed
properties, then a key-value array makes sense, and I'm not suggesting
we remove that from the language. But I do think we should be adding
richer choices beside arrays, rather than just more ways to write the
same thing.
Regards,
--
Rowan Tommins
[IMSoP]
In every place where
key
is a valid php identifier
(e.g. can be used in PHP 8.0's named parameters),
I propose to allow[key: expr]
to be used instead of['key' => expr]
.This is an immediate "no" from me: it multiplies the ways to write the same thing from 2 to 4, in order to save a few bytes, in a few instances.
I think this is something that Douglas Crockford got absolutely right when he simplified JavaScript object syntax to formalise JSON: every valid key can be represented as a quoted string, so if the quotes are always there,p ...
That is one way to look at it.
Another way to look at it was Crockford's regrettable decision to optimize for the uncommon case by imposing tedium on developers and making reading of JSON files even harder for end-users than developers has created an unfortunate legacy that we can likely never correct given the ubiquity of JSON at this point. That decision was probably the initial impetus for JSON5, even, which unfortunately can't get widespread adoption because of JSON classic's ubiquity.
But I digress. And the reason I digress is to contrast — as Tyson also did — the JSON the serialization form with the programming language form. Javascript itself supports object initialization without having quotes around the identifiers. And unless I remember wrongly, serializing the JSON object was the spark that turned into JSON.
Meanwhile back at PHP, one of the most annoying and poor developer experience aspects of working with PHP — because arrays are so ubiquitous in code in the wild — is the requirement for those damn quotes around array indexes, and especially that array indexes are not implemented as symbols so that we can get some "compile-time" checking of indexes in our IDEs and linters and even PHP itself.
you don't need to remember a list of rules about reserved words, allowed characters, etc.
Having to remember the same rules for symbol identifiers hardly seems to be a burden for a developer to me.
Also, you used etc. but I cannot think of anything else. Is there something you did not include?
This is useful for shortening long arrays where the keys are known literals,
e.g.return [success: true, data: $data, cursor: $cursor]; // is equivalent to the following, but shorter: return ['success' => true, 'data' => $data, 'cursor' => $cursor];
Although common, this is not a good use of arrays; if your keys are "known literals", they should be fields of some object:
return new Result(success: true, data: $data, cursor: $cursor);
Except that arrays in PHP can do things objects cannot do in PHP. So your assertion as an absolute is not valid.
To start with, and most important for the immutable-envious developer world is that arrays are passed by value. Unless and until we have an object equivalent for passing by value you cannot authoritatively say "your fields should always be in an object."
Also, as noted by Kami and acknowledged by Larry, there are many things you can do with arrays you cannot do with objects such as destructuring.
If you don't want to declare a class (yet), you can use an anonymous object. Rather than yet another way to write arrays, it would be great to have some more powerful syntax for those; currently you'd have something like this:
return new class(success: true, data: $data, cursor: $cursor) { public function __construct(public bool $success, public array $data, public CursorInterface $cursor) {} };
Brainstorming, we could perhaps extend property promotion into the "new class" clause, so that you could write this:
return new class(public bool success: true, public array data: $data, public CursorInterface cursor: $cursor) {};
Let's compare your suggestion with the following from a readability and also developer quality-of-life perspective:
return [ success: true, data: $data, cursor: $cursor ];
YES, there are benefits you get with the approach you mention, but readability and developer experience are definitely not in that set of benefits.
NOW, all that said, I am not sure I will support this change to arrays proposed by Tyson.
I might, but I would like to understand Tyson's use-cases first where he would prefer to need an array instead of an object if objects instantiation were made easier, and more importantly, why could objects not be used?
-Mike
P.S. While I wanted to close with a request for Tyson to explain, I also want to mention I would likely be a lot more interested in is innovation in the area of object initialization, implicit typing and implicit class declaration.
Consider if the following could create an instance of an anonymous class with an implicitly-typed public boolean property success
, and array property data
and a CursorInterface property cursor
? Wouldn't that be a lot better than the very explicit declaration of the anonymous class syntax proposed?
function Foo() {
....
return { success: true, array data: $data, CursorInterface cursor: $cursor };
}
Frankly I would also like to see it not create an anonymous class but be able to name the class then and there, e.g. something like this where FooReturn is automatically declared here as a class with default public properties:
function Foo() {
....
return as FooReturn => {
success: true,
array data: $data,
CursorInterface cursor: $cursor
};
}
These would primarily be value objects, so we could limit them to just properties. It would also make a readonly attribute useful, e.g.
function Foo() {
....
return as readonly FooReturn => {
success: true,
array data: $data,
CursorInterface cursor: $cursor
};
}
Which actually brings up an interesting question. Why can't we have declared array types? Aren't they often used no-declare value objects? At which point, why is there a difference between array usage and object usage other than parameter passing type. Why can't we use foreach and [] on objects, and -> on arrays?
I'm asking these questions in hopes others will consider out-of-the-box thinking when exploring what might be for PHP.
Am 21.06.2021 um 18:54 schrieb tyson andre tysonandre775@hotmail.com:
return [success: true, data: $data, cursor: $cursor];
// is equivalent to the following, but shorter:
return ['success' => true, 'data' => $data, 'cursor' => $cursor];
Just a little side-note: A while ago I proposed a 2-line-patch to allow :$foo as a synonym for 'foo' => $foo.
This allows for
return ['success' => true, :$data, :$cursor];
which is both shorter and removes repetition while keeping the variable usage $data and $cursor visible.
I know that this has been shot down before but I couldn't resist mentioning it in this context, sorry ;-)
- Chris
Hi Christian Schneider,
return [success: true, data: $data, cursor: $cursor];
// is equivalent to the following, but shorter:
return ['success' => true, 'data' => $data, 'cursor' => $cursor];Just a little side-note: A while ago I proposed a 2-line-patch to allow :$foo as a synonym for 'foo' => $foo.
This allows for
return ['success' => true, :$data, :$cursor];
which is both shorter and removes repetition while keeping the variable usage $data and $cursor visible.I know that this has been shot down before but I couldn't resist mentioning it in this context, sorry ;-)
I'd also implemented the same thing at https://github.com/php/php-src/pull/6635 and reverted it - didn't see that when the PR was first created
It had also suggested
:$var
or=$var
as shorthand forvar: $var
,
but this is going to be left out of this proposal
https://externals.io/message/101698 has mostly negative feedback on a recent proposal (and there are multiple syntax candidates)
That was left out - I expected it would get less votes than just var: $var
for reasons mentioned in https://externals.io/message/101698
Based on past RFCs I've seen, I'd assume an RFC would fail if most feedback was proposing alternate solutions or arguing against it, like it is here.
And voting results for https://wiki.php.net/rfc/bare_name_array_literal were mostly negative.
I'd hoped named arguments using the same syntax might raise interest in this, but it doesn't look like it so far.
E.g. short functions in https://externals.io/message/113751 had some positive feedback, but still got less than 2/3 votes
Thanks,
Tyson