Hello internals!
I was wondering, has there been any discussion about supporting local
constants (variables that cannot be reassigned, perhaps even function
parameters)?
Hello internals!
I was wondering, has there been any discussion about supporting local constants (variables that cannot be reassigned, perhaps even function parameters)?
+1 from me.
-Mike
Hello internals!
I was wondering, has there been any discussion about supporting local
constants (variables that cannot be reassigned, perhaps even function
parameters)?
As for me, I would've hoped that there were local constants in PHP and I'd
want to provide some thoughts on this regard.
In my opinion, I'd really love the following syntax:
a = 'the constant value';
I'm not sure if this would relate to the rest of PHP ecosystem, since there
are already const
/define
keywords for ordinary constants and readonly
for properties.
Nonetheless, in my opinion,
more concice syntax like a = '...';
would promote usage of local
constants instead of variables, which is generally better, being more
stricter, though being a huge change to PHP as well.
Best regards, Yevhen
Hello internals!
I was wondering, has there been any discussion about supporting local constants (variables that cannot be reassigned, perhaps even function parameters)?
Out of curiosity, what value would this bring to PHP? In my experience, modern php methods and functions tend to fit on a single screen, at most being a few hundred lines for complex logic.
— Rob
Having constant parameters would be a tremendous help in refactoring old
code. I would love having local constants.
Hello internals!
I was wondering, has there been any discussion about supporting local constants (variables that cannot be reassigned, perhaps even function parameters)?
Out of curiosity, what value would this bring to PHP? In my experience, modern php methods and functions tend to fit on a single screen, at most being a few hundred lines for complex logic.
— Rob
This is about semantic clarity. If you define a variable as a constant, then, not only do you know for certain that it cannot change, you are also stating "it is my intent to not change this variable after setting it", and so if someone later tries to do so, they (should) have to answer the question, "why do I need to make this variable change?".
If it's useful to be able to annotate class members as being "readonly", it is likewise useful to do that on a local scope.
With sufficient compiler support, typed constant variable declarations might also allow for this:
let SomeType $foo; //or readonly, or writeonce, or whatever
if($something) {
$foo = ...;
} else if($somethingElse) {
$foo = getSomeString(); //Error, type mismatch; possibly catchable at compile time, definitely with static analysis
} else if($thirdCondition) {
$foom = ...; //oops, typo
} else {
throw new Exception(...)
}
doSomething($foo); //Compiler error: $foo not initialized on all call paths.
}
You might also tighten scoping with such variables:
foreach(...) {
let $foo = //local working variable; possibly even shadowing a variable in the parent scope
}
//$foo is undefined; you can't access the last-set value from the loop outside of the loop
Static analysis can catch all these errors, but it'd be nicer to be able to do it without requiring docblocks. The language providing tools for being more explicit about intent in code is never bad.
-John
Hello internals!
I was wondering, has there been any discussion about supporting local constants (variables that cannot be reassigned, perhaps even function parameters)?
Out of curiosity, what value would this bring to PHP? In my experience, modern php methods and functions tend to fit on a single screen, at most being a few hundred lines for complex logic.
— Rob
This is about semantic clarity. If you define a variable as a constant, then, not only do you know for certain that it cannot change, you are also stating "it is my intent to not change this variable after setting it", and so if someone later tries to do so, they (should) have to answer the question, "why do I need to make this variable change?".
If it's useful to be able to annotate class members as being "readonly", it is likewise useful to do that on a local scope.
With sufficient compiler support, typed constant variable declarations might also allow for this:
let SomeType $foo; //or readonly, or writeonce, or whatever
if($something) {
$foo = ...;
} else if($somethingElse) {
$foo = getSomeString(); //Error, type mismatch; possibly catchable at compile time, definitely with static analysis
} else if($thirdCondition) {
$foom = ...; //oops, typo
} else {
throw new Exception(...)
}doSomething($foo); //Compiler error: $foo not initialized on all call paths.
}You might also tighten scoping with such variables:
foreach(...) {
let $foo = //local working variable; possibly even shadowing a variable in the parent scope
}//$foo is undefined; you can't access the last-set value from the loop outside of the loop
Static analysis can catch all these errors, but it'd be nicer to be able to do it without requiring docblocks. The language providing tools for being more explicit about intent in code is never bad.
-John
I think, conceptually, this makes sense, but maybe I'm the only one who writes
$arr = doSomething();
$arr = array_map(fn($x) => $x->prop, $arr);
$arr = array_filter($arr, $filterFunc);
instead of
$arr = array_filter(array_map(fn($x) => $x->prop, doSomething()), $filterFunc);
And I feel like having constant variables would result in more of the latter, which I feel is more unreadable. Though I do note that my opinion of what is readable might be different from other people's.
That being said, I would much rather see block-scoped variables than local variables via let or var or something:
var $aNumber = 12;
foreach($arr as var $item) {
echo $item; // item exists here
}
echo $item; // item doesn't exist here
PHP is simply too verbose to really benefit from local constants, but would benefit from block-scope far more. For example, with local constants, you couldn't write that foreach because that variable exists in the scope of the function and can only be defined once.
— Rob
I think, conceptually, this makes sense, but maybe I'm the only one who writes
$arr = doSomething();
$arr = array_map(fn($x) => $x->prop, $arr);
$arr = array_filter($arr, $filterFunc);instead of
$arr = array_filter(array_map(fn($x) => $x->prop, doSomething()), $filterFunc);
IMO, that's a failing of PHP not supporting object syntax (and reasonable api) for non-object values. In other languages, I can write code such as:
let arr = doSomething()
.map { $0.prop }
.filter(filterFunc)
Which is more readable than both PHP versions.
And I feel like having constant variables would result in more of the latter, which I feel is more unreadable. Though I do note that my opinion of what is readable might be different from other people's.
That being said, I would much rather see block-scoped variables than local variables via let or var or something:
var $aNumber = 12;
foreach($arr as var $item) {
echo $item; // item exists here
}echo $item; // item doesn't exist here
PHP is simply too verbose to really benefit from local constants, but would benefit from block-scope far more. For example, with local constants, you couldn't write that foreach because that variable exists in the scope of the function and can only be defined once.
— Rob
I'd suggest that the foreach could be written with either of let/var to both locally scope $item, which would explicitly disallow/allow $item to be changed while in the loop.
Another benefit of local constants is preventing accidentally reusing a prior variable. For example, if I set a temporary variable to something, and then later in the function, reuse that same name for a new temporary but don't set the value on all flow paths, then the value from earlier in the function leaks through, whereas either using a constant, or a second explicit variable definition would have exposed and/or prevented the issue.
-John
I think, conceptually, this makes sense, but maybe I'm the only one who writes
$arr = doSomething();
$arr = array_map(fn($x) => $x->prop, $arr);
$arr = array_filter($arr, $filterFunc);instead of
$arr = array_filter(array_map(fn($x) => $x->prop, doSomething()), $filterFunc);
IMO, that's a failing of PHP not supporting object syntax (and reasonable api) for non-object values. In other languages, I can write code such as:
let arr = doSomething()
.map { $0.prop }
.filter(filterFunc)Which is more readable than both PHP versions.
I think that sidesteps the reality we are currently in and is orthogonal to the actual problem I perceive. To be more clear, I think with the current APIs in PHP, having local constants would incentivize people to write less readable code for the reasons you mentioned. Could the APIs be improved? Probably, if not certainly, but that is not what is on the table.
And I feel like having constant variables would result in more of the latter, which I feel is more unreadable. Though I do note that my opinion of what is readable might be different from other people's.
That being said, I would much rather see block-scoped variables than local variables via let or var or something:
var $aNumber = 12;
foreach($arr as var $item) {
echo $item; // item exists here
}echo $item; // item doesn't exist here
PHP is simply too verbose to really benefit from local constants, but would benefit from block-scope far more. For example, with local constants, you couldn't write that foreach because that variable exists in the scope of the function and can only be defined once.
— Rob
I'd suggest that the foreach could be written with either of let/var to both locally scope $item, which would explicitly disallow/allow $item to be changed while in the loop.
Maybe, but I only mention that block-scope would be more valuable than local constants. That they work well together is also interesting.
Another benefit of local constants is preventing accidentally reusing a prior variable. For example, if I set a temporary variable to something, and then later in the function, reuse that same name for a new temporary but don't set the value on all flow paths, then the value from earlier in the function leaks through, whereas either using a constant, or a second explicit variable definition would have exposed and/or prevented the issue.
-John
This is a problem that I have run into in only a handful of times in my entire career, across all languages, lots of times in JavaScript files of yesteryear when we had to deal with 10k loc handrolled files. I’ve seen this happen maybe 2-3 times in php (where it has been a bug), and a couple of times in C. I don’t think I’ve ever run into it in C#, Scala, or Python. Maybe I’m just lucky, but I don’t feel like this is a valid reason for the feature.
Const was added to JavaScript (according to my cobwebbed memories) to introduce block scoping and optimizations around that. In PHP, we also have function scope, like JavaScript, but we also have lexical scope as well. Javascript did not have that at the time; in other words, the following would have output "world":
function test() {
console.log(hello)
var hello = 'world'
}
test();
Today, it doesn't, and neither would php. The problems that const/let set out to solve in Javascript do not apply here, IMHO.
— Rob
I think, conceptually, this makes sense, but maybe I'm the only one who writes
$arr = doSomething();
$arr = array_map(fn($x) => $x->prop, $arr);
$arr = array_filter($arr, $filterFunc);instead of
$arr = array_filter(array_map(fn($x) => $x->prop, doSomething()), $filterFunc);
IMO, that's a failing of PHP not supporting object syntax (and reasonable api) for non-object values. In other languages, I can write code such as:
let arr = doSomething()
.map { $0.prop }
.filter(filterFunc)Which is more readable than both PHP versions.
I think that sidesteps the reality we are currently in and is orthogonal to the actual problem I perceive. To be more clear, I think with the current APIs in PHP, having local constants would incentivize people to write less readable code for the reasons you mentioned.
It could ALSO incentivize people to write their own wrappers — easily done in userland — or include a package that has those wrappers already written, and then they would be able to write more easily-readable code like this:
const WIDGET = getWidgets()
->map(fn($w) =>$w->prop)
->filter($filterFunc);
Further, as part of the announcement of the new feature that PHP we could write that PHP considers it a best practice to use said wrappers rather than to write code in the less readable format you presented.
And if people instead do as you fear it would provide more incentive for PHP to add such quality-of-life improvements into core.
Maybe, but I only mention that block-scope would be more valuable than local constants. That they work well together is also interesting.
Block-scope? Please, just no.
As someone who also develops in Go, one of the biggest mistakes they made in language design IMO is block-scoped variables. In my anecdotal opinion, block-scoping is one of the more insidious foot-guns in the language as it is all too easy to accidentally shadow a variable that you did not intend to shadow which can result in a hard-to-track down bug.
Not convinced? At least one other person has my same view of the dark despair of block-scoping:
https://forum.golangbridge.org/t/warning-for-accidental-variable-shadowing-with-block-scope/4715
Of all the Go code I have written, I'd say accidental variable shadowing is in the top-three (3) reasons for bugs in my code, if not the main reason. And if it were not for BC breaks, I would lobby hard for Go to remove it. I know that some on the Go team lament that they ever included it.
So while block-scoping appears to be a panacea, it is really a wolf in sheep's clothing.
This is a problem that I have run into in only a handful of times in my entire career, across all languages, lots of times in JavaScript files of yesteryear when we had to deal with 10k loc handrolled files. I’ve seen this happen maybe 2-3 times in php (where it has been a bug), and a couple of times in C. I don’t think I’ve ever run into it in C#, Scala, or Python. Maybe I’m just lucky, but I don’t feel like this is a valid reason for the feature.
I have run into this problem a handful of times in the past month including in other people's code, so clearly one person's experience is not universal.
Const was added to JavaScript (according to my cobwebbed memories) to introduce block scoping and optimizations around that. In PHP, we also have function scope, like JavaScript, but we also have lexical scope as well. Javascript did not have that at the time; in other words, the following would have output "world":
<snip>
Today, it doesn't, and neither would php. The problems that const/let set out to solve in Javascript do not apply here, IMHO.
There are other languages that exists outside the web bubble of PHP and JavaScript, and most have local constants for which "the reasons they were added to JavaScript do not apply to PHP" — a claim for which I do not know enough of this specific history to opine — do not apply: Java/Kotlin, Swift, Go, C/C++, Zig, and Rust (while ignoring TypeScript given its roots in JS.)
Simply put the reason for local const is to declare an immutable variable, one whose value we know cannot change. One key reason relevant to PHP is to ensure its value is not changed by reference by a function you are calling, but also so that you don't accidentally change it in the same function. And here are some more reasons elaborated:
https://stackoverflow.com/questions/622664/what-is-immutability-and-why-should-i-worry-about-it
-Mike
Not convinced? At least one other person has my same view of the dark despair of block-scoping:
https://forum.golangbridge.org/t/warning-for-accidental-variable-shadowing-with-block-scope/4715
Of all the Go code I have written, I'd say accidental variable shadowing is in the top-three (3) reasons for bugs in my code, if not the main reason. And if it were not for BC breaks, I would lobby hard for Go to remove it. I know that some on the Go team lament that they ever included it.
Block-scoping doesn't have to allow shadowing of names, and it certainly doesn't have to be achieved by that incredibly hard to spot syntax. In most languages I've seen, it's indicated by either a keyword such as "let", "var", or "my"; or by indicating the type of the variable you're declaring.
It's something I'd really like to see in PHP. For one thing, it would make me much more likely to accept the repeated requests for closures which capture parent scope by default, because it would give a clear opt-out to replace the current clear opt-in of the use() clause.
I've never found myself wishing for local constants, but never really used a language with them, so maybe I'd find them valuable if they were added.
For completeness, I'll mention that typed local variables are another related concept, which has also come up in the past.
The big challenge in all of them is that these are big fundamental language changes, which require an expert knowledge of how the language works under the hood to get much beyond "that would be nice to have". Let's not get too deep into discussing the colour of the bikeshed before we've worked out if there's space to build one.
Regards,
Rowan Tommins
[IMSoP]
Block-scoping doesn't have to allow shadowing of names,
How exactly would that work? Are you suggesting to restrict the use of variable names to one per function but still allow for block scoping?
Or if not, how would an inner block variable $x not shadow an outer variable $x? Shadowing is not a feature that is added, it is the result of block scoping, AFAIK.
and it certainly doesn't have to be achieved by that incredibly hard to spot syntax. In most languages I've seen, it's indicated by either a keyword such as "let", "var", or "my"; or by indicating the type of the variable you're declaring.
That declaration syntax is not what makes it problematic, it is the number of lines between the two declarations. I would not be so against it if most people kept their functions to 25 lines or less, but what have I have seen to think 200 to 500 line functions are the norm in legacy code I've had to work on.
But then if most people kept their functions small there would be far less "need" for block scoping. (Actually — to channel Rob — block scoping could encourage people to wrote longer functions which would definitely be a negative outcome.)
For one thing, it would make me much more likely to accept the repeated requests for closures which capture parent scope by default, because it would give a clear opt-out to replace the current clear opt-in of the use() clause.
Scoping within a closure differs from in-line blocks having their own scope. For one, a closure is separated by a parameter stack whereas a block shares the stack with its function. Closures can be assigned to variables and objects properties and also passed to functions, blocks cannot. Closures have their own return values, blocks have no return statements thus are coupled to the function's scope. And finally and most importantly, the point of a closure is to delay or delegate execution whereas blocks execute inline with the function where they are located. For all these reasons local scope makes sense in closures whereas IMO scope in blocks does not.
It's something I'd really like to see in PHP.
Be careful what you wish for. But don't say I did not warn you if it comes to pass.
I've never found myself wishing for local constants, but never really used a language with them, so maybe I'd find them valuable if they were added.
Ignorance is bliss. And I mean that literally, not pejoratively.
For completeness, I'll mention that typed local variables are another related concept, which has also come up in the past.
You'll get no argument from me on type local vars.
The big challenge in all of them is that these are big fundamental language changes, which require an expert knowledge of how the language works under the hood
Really? I believe what the OP was asking for — or at least it is what I would be interested in — are immutable variables that we could conveniently declare with const
. I could be wrong but it seems the only thing it would require is the compiler to throw an error when asked to generate a variable assignment opcode to any variable declared as a const.
The above assumes piggybacking on variables with $
syntax vs. no-$
syntax, so maybe using const
would not be the best keyword. We could use var
but that doesn't indicate immutability.
Or it may possible that implementing as const
w/o $
syntax would not be hard given that global const names are already recognized by the parser. And they could internally be kept on the same stack as variables.
Let's not get too deep into discussing the colour of the bikeshed before we've worked out if there's space to build one.
It is ironic you say we should make sure it's possible before we discuss if we want it. I know I have heard the opposite many times on this list — make sure we even want it before we worry about how to implement — although I cannot say for sure that it was ever you that said that.
Whatever the case, of the features that have been implemented in recent years I find it hard to believe adding local constants would be nearly as hard as some of those features. Certainly easier than aviz.
-Mike
Block-scoping doesn't have to allow shadowing of names,
How exactly would that work? Are you suggesting to restrict the use of
variable names to one per function but still allow for block scoping?
Yes. That's how C# works, for example - variables are scoped to the block where they're declared, but each name declared must be unique, not shadowing one that's currently in scope.
Shadowing is not a feature that is added, it is the result
of block scoping, AFAIK.
Shadowing is absolutely a feature that has to be implemented: the compiler has to store some "backup" of the old name binding, so that it can be "restored" when the shadowing variable goes out of scope. Without shadowing, it's instead about tracking the "lifetime" of the variable, and refusing references to it outside of the block where it's "live". I'm sure the implementation of both versions varies wildly.
It is ironic you say we should make sure it's possible before we
discuss if we want it. I know I have heard the opposite many times on
this list — make sure we even want it before we worry about how to
implement — although I cannot say for sure that it was ever you that
said that.
You're right, there is some value in letting people know that a feature would be popular if implemented. I'm just aware that threads like this can quickly grow into rambling discussions of wild ideas with little grounding in what's possible, or back-and-forth debates between two people about something completely hypothetical. When that happens, those with the experience to actually implement big new features tend to tune out.
I will now try to practice what I preach, and not post further in this thread unless it moves onto something more concrete.
Regards,
Rowan Tommins
[IMSoP]
Block-scoping doesn't have to allow shadowing of names,
How exactly would that work? Are you suggesting to restrict the use of
variable names to one per function but still allow for block scoping?Yes. That's how C# works, for example - variables are scoped to the block where they're declared, but each name declared must be unique, not shadowing one that's currently in scope.
Interesting. That seems... an odd choice, just to enable block scoping.
BTW, I find block scoping has more pain than pleasure, even when it is not exactly causing bugs, e.g.:
if (is_null($widget = getWidget())) {
return;
}
echo $widget->name; // block-scoped $widget not available
The above would actually be a runtime bug in PHP, if the code were not covered by testing beforehand. In a statically-typed pre-compiled language like Go, it is just an annoyance.
OTOH, I very rarely ever find a situation where I am actually happy to have block scoping. IOW, I don't know what it enables that make people want it so badly, except possibly conflating closures with scoped-blocks, or ability to further control scoping in (too) long functions.
I think features that would reward people for writing shorter functions would be much better, but maybe that is just me.
Shadowing is not a feature that is added, it is the result
of block scoping, AFAIK.Shadowing is absolutely a feature that has to be implemented: the compiler has to store some "backup" of the old name binding, so that it can be "restored" when the shadowing variable goes out of scope. Without shadowing, it's instead about tracking the "lifetime" of the variable, and refusing references to it outside of the block where it's "live". I'm sure the implementation of both versions varies wildly.
Meh, you can twist words to mean that, but your words ignore that all variable scoping is then explicitly implemented as feature.
What I meant was the language already implements variable scoping so there is no additional work needed beyond what they language already implements. Unless the language was weirdly designed in such a way that it cannot reuse existing scoping mechanisms, which I will admit may actually be true of PHP.
OTOH, I am not speaking of what you just revealed about C# which I find to be also be, weird.
You're right, there is some value in letting people know that a feature would be popular if implemented. I'm just aware that threads like this can quickly grow into rambling discussions of wild ideas with little grounding in what's possible, or back-and-forth debates between two people about something completely hypothetical. When that happens, those with the experience to actually implement big new features tend to tune out.
With respect, local constants and/or immutable local variables are hardly "wild ideas," nor likely something that has "little grounding in what's possible."
-Mike