Constants and Access Modifiers

php.internals

Fleshgrinder

8 years ago
Hey Internals! We currently have a couple of things that are broken about constants, and I wanted to gauge if people are interested in a) fixing it as well as b) to get to know the root causes of these things. # 1 A constant defined in an interface cannot be overwritten in the class that implements the interface, however, further subclasses can overwrite the content of the constant at will. - https://3v4l.org/DFB37 - https://3v4l.org/9LMci A constant defined in a class can be overwritten by any subclass at will. # 2 Constants can reference themselves, and subclasses can define the actual value. Kind of like abstract constants. This works nicely while working with an actual instance of the object, however, it breaks in the moment that constant is accessed anywhere in the parent, or from any other constant. - https://3v4l.org/HUCTh - https://3v4l.org/5aYB5 # 3 A constant that is defined with a visibility in a parent class, cannot be references between subclasses, like it is possible with methods. Instead we are presented with an access violation. - https://3v4l.org/2lU3i Note that this behavior is the same for static and instance properties that are being redefined in a child class. Hence, access is defined by location and not by modifier. # What I think I haven't thought very long about everything, but here are my initial thoughts: ## 1 This issue could be resolved by changing the behavior to enable overwriting of parent constant in any sublcass. This gives us a consistent behavior. Afterwards we should add support for the final modifier, so that people can seal their constants and protect them from redefinition.
> Why not seal by default?
It would disallow some dynamic programming that is possible with the late static binding of PHP, and I honestly see no reason why we should disallow something that is already possible: BC! ## 2 Disallow self-referencing constants in any context, and instead add support for the abstract keyword to constants. This raises the question on how to deal with constants in interfaces. I would allow the definition of both constants with and without a value there. Those without are abstract, those with a value are like they are right now. Directly referencing an abstract constant should result in an error. Abstract constants are a great thing in combination with late static binding, and the engine ensures that the value does not change over the course of the runtime of the program. An attribute that is impossible for methods. Dart for instance has support for the const keyword for many elements, including methods. ## 3 This seems like a bigger construction site. I think that the behavior should be that the access is determined by the modifier, and not the location. Especially because doing anything else is to great of a BC. The current behavior of allowing access to protected members of other classes with the same parent also allows the creation of friend classes. Although without the control that real friend classes have.
-- Richard "Fleshgrinder" Fussenegger

Nikita Popov

8 years ago
On Fri, Oct 27, 2017 at 10:51 PM, Fleshgrinder <php@fleshgrinder.com> wrote:
> Hey Internals! > > We currently have a couple of things that are broken about constants, > and I wanted to gauge if people are interested in a) fixing it as well > as b) to get to know the root causes of these things. > > # 1 > > A constant defined in an interface cannot be overwritten in the class > that implements the interface, however, further subclasses can overwrite > the content of the constant at will. > > - https://3v4l.org/DFB37 > - https://3v4l.org/9LMci > > A constant defined in a class can be overwritten by any subclass at will. > > # 2 > > Constants can reference themselves, and subclasses can define the actual > value. Kind of like abstract constants. This works nicely while working > with an actual instance of the object, however, it breaks in the moment > that constant is accessed anywhere in the parent, or from any other > constant. > > - https://3v4l.org/HUCTh > - https://3v4l.org/5aYB5
PHP does not permit self-referencing constants. However, this is only checked when the constant is first accessed. In your first example the constant is never accessed, so no error is thrown. This has nothing to do with subclasses defining the value -- you're using late static binding, so you're accessing the constant of the child class directly. PHP cannot detect self-referencing constants during compilation, because they may be formed through non-trivial cycles involving multiple constants, across multiple files. Nikita # 3

Fleshgrinder

8 years ago
On 10/27/2017 11:15 PM, Nikita Popov wrote:
> PHP does not permit self-referencing constants. > > However, this is only checked when the constant is first accessed. In your > first example the constant is never accessed, so no error is thrown. This > has nothing to do with subclasses defining the value -- you're using late > static binding, so you're accessing the constant of the child class > directly. > > PHP cannot detect self-referencing constants during compilation, because > they may be formed through non-trivial cycles involving multiple constants, > across multiple files. > > Nikita >
My wording was maybe a bit wrong here, and I was biased by the fact that I would like to see abstract constants. The fact that not everything can be detected at compile time, but only later at runtime is normal in a highly dynamic language like PHP. Self-referencing constants make no sense, hence, it is fine. It would of course be better if the compiler could detect that earlier, but we are not doing AOT so imho that is fine. The behavior here is also consistent among versions as well as HHVM, all good. What do you think about the other ideas I raised?
-- Richard "Fleshgrinder" Fussenegger

Stas Malyshev

8 years ago
Hi!
> My wording was maybe a bit wrong here, and I was biased by the fact that > I would like to see abstract constants. The fact that not everything can
What is "abstract constant"? If you need something that can change, just use a method. Constant is meant to be a nice way to write something inherently constant, such as instead of "/very long (and+)cubmber|some?*regexp/" you'd write NICE_REGEXP_CONSTANT. But it's not supposed to create parallel inheritance structure or something. If it needs to be non-constant, just use a method.
-- Stas Malyshev smalyshev@gmail.com

Niklas Keller

8 years ago
> > Hi! > > > My wording was maybe a bit wrong here, and I was biased by the fact that > > I would like to see abstract constants. The fact that not everything can > > What is "abstract constant"? If you need something that can change, just > use a method. Constant is meant to be a nice way to write something > inherently constant, such as instead of "/very long > (and+)cubmber|some?*regexp/" you'd write NICE_REGEXP_CONSTANT. But it's > not supposed to create parallel inheritance structure or something. If > it needs to be non-constant, just use a method. >
Totally agree with that. We should deprecate constant inheritance instead. Regards, Niklas

Fleshgrinder

8 years ago
On 11/5/2017 1:03 PM, Niklas Keller wrote:
>> >> Hi! >> >>> My wording was maybe a bit wrong here, and I was biased by the fact that >>> I would like to see abstract constants. The fact that not everything can >> >> What is "abstract constant"? If you need something that can change, just >> use a method. Constant is meant to be a nice way to write something >> inherently constant, such as instead of "/very long >> (and+)cubmber|some?*regexp/" you'd write NICE_REGEXP_CONSTANT. But it's >> not supposed to create parallel inheritance structure or something. If >> it needs to be non-constant, just use a method. >> > > Totally agree with that. We should deprecate constant inheritance instead. > > Regards, Niklas >
An abstract constant is a constant that requires its value to be defined later, like an abstract method that requires its implementation to be defined later. The thing is that I want something that CANNOT CHANGE. I want to require that you define the value, a value that is known at compile time, a value that is immutable, a value that can never changes during the program's execution. This is not achievable by current, available means. What you describe is the intention of the current class constant implementation of PHP, and some other languages. Pure logic does not support this claim. The only thing a constant should provide is that its value is known at compile time. That being said, we already violate that by supporting dynamic constant definitions via define, but let's not go down that road here. Dropping support for constant inheritance is imho also wrong from a pure logical point of view, since a constant's value is only constant if I refer to the very same constant. Meaning, the value of a constant in a subclass must not be the same value as the value in the superclass. class Integer extends Number { public const MAX = \PHP_INT_MAX; public const MIN = \PHP_INT_MIN; } class WholeNumber extends Integer { public const MIN = 0; } class NaturalNumber extends WholeNumber { public const MIN = 1; } We expect that `Integer::MIN` always yields the same value, rightly so, since it is a constant. However, nobody expects that `NaturalNumber::MIN` is going to yield the same value as `Integer::MIN` does, simply because it is a different value. Of course we expect it to be compatible, they are after all in a tight relationship (inheritance). They also have to be compatible to each other, otherwise we would violate the substitutability. I mentioned Dart in the initial message, they have a much richer understanding of const than we have it in PHP. Maybe this also helps to broaden your views on the topic: https://news.dartlang.org/2012/06/const-static-final-oh-my.html
-- Richard "Fleshgrinder" Fussenegger

Stas Malyshev

8 years ago
Hi!
> An abstract constant is a constant that requires its value to be defined > later, like an abstract method that requires its implementation to be > defined later.
It's not a constant then, and should be a method.
> > The thing is that I want something that CANNOT CHANGE. I want to require
You are contradicting yourself. If it is not known upfront, then it can change - otherwise, you'd know it upfront. I'm not sure what you're trying to do here, but I am getting pretty sure you shouldn't be doing it with const's :)
> Dropping support for constant inheritance is imho also wrong from a pure > logical point of view, since a constant's value is only constant if I > refer to the very same constant. Meaning, the value of a constant in a > subclass must not be the same value as the value in the superclass. > > class Integer extends Number { > public const MAX = \PHP_INT_MAX; > public const MIN = \PHP_INT_MIN; > } > > class WholeNumber extends Integer { > public const MIN = 0; > } > > class NaturalNumber extends WholeNumber { > public const MIN = 1; > }
Integer::MIN and NaturalNumber::MIN are different constants. So, it is natural that they can have different values. Though using constants with these names is slightly misleading, but if you always use full name, not by much.
> does, simply because it is a different value. Of course we expect it to > be compatible, they are after all in a tight relationship (inheritance).
Here you are getting into a dangerous territory, btw. Depending on your modeling needs, of course, but your NaturalNumber can violate contract of Integer, such as "being able to represent -10". Thus, inheritance could be wrong way to do it, at least in the way you described above. One has to be very careful with which exactly contract are you modelling - inheritance is not just shortcut for avoiding copy-paste.
> I mentioned Dart in the initial message, they have a much richer > understanding of const than we have it in PHP. Maybe this also helps to > broaden your views on the topic: > > https://news.dartlang.org/2012/06/const-static-final-oh-my.html
From this link, it looks like const in Dart has pretty much nothing in common with const in PHP, besides name, so in the interest of avoiding confusion, I would not discuss it in the same topic.
-- Stas Malyshev smalyshev@gmail.com

Fleshgrinder

8 years ago
On 11/6/2017 1:44 AM, Stanislav Malyshev wrote:
> Hi! > >> An abstract constant is a constant that requires its value to be defined >> later, like an abstract method that requires its implementation to be >> defined later. > > It's not a constant then, and should be a method. >
No, because a method can change what it returns, it is not constant. On 11/6/2017 1:44 AM, Stanislav Malyshev wrote:
>> >> The thing is that I want something that CANNOT CHANGE. I want to require > > You are contradicting yourself. If it is not known upfront, then it can > change - otherwise, you'd know it upfront. I'm not sure what you're > trying to do here, but I am getting pretty sure you shouldn't be doing > it with const's :) >
No, I want to split the definition and initialization. The value cannot change, once it is initialized. On 11/6/2017 1:44 AM, Stanislav Malyshev wrote:
>> Dropping support for constant inheritance is imho also wrong from a pure >> logical point of view, since a constant's value is only constant if I >> refer to the very same constant. Meaning, the value of a constant in a >> subclass must not be the same value as the value in the superclass. >> >> class Integer extends Number { >> public const MAX = \PHP_INT_MAX; >> public const MIN = \PHP_INT_MIN; >> } >> >> class WholeNumber extends Integer { >> public const MIN = 0; >> } >> >> class NaturalNumber extends WholeNumber { >> public const MIN = 1; >> } > > Integer::MIN and NaturalNumber::MIN are different constants. So, it is > natural that they can have different values. Though using constants with > these names is slightly misleading, but if you always use full name, not > by much. >
I do not follow, what is misleading about them? Referencing them without the full name is also not possible in PHP, hence, not an issue. On 11/6/2017 1:44 AM, Stanislav Malyshev wrote:
>> does, simply because it is a different value. Of course we expect it to >> be compatible, they are after all in a tight relationship (inheritance). > > Here you are getting into a dangerous territory, btw. Depending on your > modeling needs, of course, but your NaturalNumber can violate contract > of Integer, such as "being able to represent -10". Thus, inheritance > could be wrong way to do it, at least in the way you described above. > One has to be very careful with which exactly contract are you modelling > - inheritance is not just shortcut for avoiding copy-paste. >
I am not sure how you deduce that NaturalNumber can be -10. There are not invariants defined anywhere on the classes. You are correct that inheritance should not be misused for code reuse, it should be used to build type systems. Which is exactly what I am doing in the example. On 11/6/2017 1:44 AM, Stanislav Malyshev wrote:
>> I mentioned Dart in the initial message, they have a much richer >> understanding of const than we have it in PHP. Maybe this also helps to >> broaden your views on the topic: >> >> https://news.dartlang.org/2012/06/const-static-final-oh-my.html > > From this link, it looks like const in Dart has pretty much nothing in > common with const in PHP, besides name, so in the interest of avoiding > confusion, I would not discuss it in the same topic. >
Yes, Dart has a different understanding of const, which is exactly why I posted it for you guys. In the hope that it helps to get more different views on the topic. Currently you are too concentrated on how it is implemented in PHP at this time, and argue that it is impossible to diverge from that path. Which is simply not true, we only have to ensure backwards compatibility.
-- Richard "Fleshgrinder" Fussenegger

Rowan Collins

8 years ago
On 11/11/2017 18:39, Fleshgrinder wrote:
> On 11/6/2017 1:44 AM, Stanislav Malyshev wrote: > >> From this link, it looks like const in Dart has pretty much nothing in >> common with const in PHP, besides name, so in the interest of avoiding >> confusion, I would not discuss it in the same topic. >> > Yes, Dart has a different understanding of const, which is exactly why I > posted it for you guys. In the hope that it helps to get more different > views on the topic. Currently you are too concentrated on how it is > implemented in PHP at this time, and argue that it is impossible to > diverge from that path. Which is simply not true, we only have to ensure > backwards compatibility.
I think the point is that adding "single-assignment variables" (which Dart calls "final") or "deeply immutable values" (which Dart calls "const") would be a completely different feature which could be added side-by-side with what we have now, under a different name. What PHP calls "const" (in the context of classes) is neither of those features. The debate seems to be whether you view class constants as more like static properties, or static methods. Given this: class A { public const Foo = 42; } echo A::Foo; Is it equivalent to this (using an imaginary "readonly" modifier)... class A { public static readonly $foo = 42; } echo A::$foo; ...or is it equivalent to this (particularly if you imagine an optimising compiler that caches / inlines the result)? class A { public static function foo(): int { return 42; } } echo A::foo(); The difference is that a field is never "abstract" - it either has a value, or it is undefined; you can't add a contract to an interface saying "you must have this field" either. A method, on the other hand, is assumed to encapsulate something - it's a black box with a contract, so defining the contract without any implementation makes sense. (Note that I've called $foo a "field" to distinguish it from a "property", as C# does: a property with a contract like "$foo { public get; private set; }" could indeed be abstract.) The interesting thing about the above examples is that the static method with a fixed return *already works right now*, whereas the readonly field doesn't exist; so it makes some sense to say that "const FOO" is PHP's way of saying "static readonly $FOO", and have it subject to similar semantics. Regards,
-- Rowan Collins [IMSoP]

Fleshgrinder

8 years ago
On 11/11/2017 9:51 PM, Rowan Collins wrote:
> On 11/11/2017 18:39, Fleshgrinder wrote: >> On 11/6/2017 1:44 AM, Stanislav Malyshev wrote: >> >>>  From this link, it looks like const in Dart has pretty much nothing in >>> common with const in PHP, besides name, so in the interest of avoiding >>> confusion, I would not discuss it in the same topic. >>> >> Yes, Dart has a different understanding of const, which is exactly why I >> posted it for you guys. In the hope that it helps to get more different >> views on the topic. Currently you are too concentrated on how it is >> implemented in PHP at this time, and argue that it is impossible to >> diverge from that path. Which is simply not true, we only have to ensure >> backwards compatibility. > > I think the point is that adding "single-assignment variables" (which > Dart calls "final") or "deeply immutable values" (which Dart calls > "const") would be a completely different feature which could be added > side-by-side with what we have now, under a different name. What PHP > calls "const" (in the context of classes) is neither of those features. >
I know and I repeat, I posted the link just to show that const can be more than the const we currently have, and to fuel the discussion. I never said that we have to have the same implementation they have. ;) On 11/11/2017 9:51 PM, Rowan Collins wrote:
> The debate seems to be whether you view class constants as more like > static properties, or static methods. Given this: > > class A { public const Foo = 42; } > echo A::Foo; > > Is it equivalent to this (using an imaginary "readonly" modifier)... > > class A { public static readonly $foo = 42; } > echo A::$foo; > > ...or is it equivalent to this (particularly if you imagine an > optimising compiler that caches / inlines the result)? > > class A { public static function foo(): int { return 42; } } > echo A::foo(); > > > The difference is that a field is never "abstract" - it either has a > value, or it is undefined; you can't add a contract to an interface > saying "you must have this field" either. A method, on the other hand, > is assumed to encapsulate something - it's a black box with a contract, > so defining the contract without any implementation makes sense. (Note > that I've called $foo a "field" to distinguish it from a "property", as > C# does: a property with a contract like "$foo { public get; private > set; }" could indeed be abstract.) > > The interesting thing about the above examples is that the static method > with a fixed return *already works right now*, whereas the readonly > field doesn't exist; so it makes some sense to say that "const FOO" is > PHP's way of saying "static readonly $FOO", and have it subject to > similar semantics. > > Regards, >
Other languages allow you to have a contract on fields. class A { const FOO: int = 42; } class A { final static $foo: int = 42; } These are basically the same, as you already said. However, I added a contract to both of them. There is one thing that differs for the const and the field: a const value must be known at compile time, whereas a field value does not. An important difference! class A { abstract public const FOO: int; } class A { abstract public function foo(): int; } These also look basically the same. The return value of the method, however, may also be determined at runtime (just like with fields) and on top of that might change with every invocation. As I said, the idea is to have a constant value that is known at compile time. This is the definition of const in PHP. The only thing I was asking for is to split definition and initialization by reusing the abstract keyword.
-- Richard "Fleshgrinder" Fussenegger

Stas Malyshev

8 years ago
Hi!
> Yes, Dart has a different understanding of const, which is exactly why I > posted it for you guys. In the hope that it helps to get more different > views on the topic. Currently you are too concentrated on how it is > implemented in PHP at this time, and argue that it is impossible to > diverge from that path. Which is simply not true, we only have to ensure > backwards compatibility.
I am not arguing it's impossible, I am arguing it is not a good idea. We have the concept of constants in this language, and bolting on it a completely different concept from different language, which by coincidence was named with the same term, would only be a source of confusion. If we wanted immutable objects in language - which I am not convinced at all we do, but assuming for a minute we did - there's no reason to conflate them with constants as we have them now. These are different things.
-- Stas Malyshev smalyshev@gmail.com

Fleshgrinder

8 years ago
On 11/12/2017 12:44 AM, Stanislav Malyshev wrote:
> Hi! > >> Yes, Dart has a different understanding of const, which is exactly why I >> posted it for you guys. In the hope that it helps to get more different >> views on the topic. Currently you are too concentrated on how it is >> implemented in PHP at this time, and argue that it is impossible to >> diverge from that path. Which is simply not true, we only have to ensure >> backwards compatibility. > > I am not arguing it's impossible, I am arguing it is not a good idea. We > have the concept of constants in this language, and bolting on it a > completely different concept from different language, which by > coincidence was named with the same term, would only be a source of > confusion. If we wanted immutable objects in language - which I am not > convinced at all we do, but assuming for a minute we did - there's no > reason to conflate them with constants as we have them now. These are > different things. >
I did not mean to say that we have to have everything exactly as Dart has it. I just wanted to show, that the meaning of const as we have is not universally the same. Abstract constants would also only be truly useful if we could define the type as well on them. Which is currently not possible. Also, I am not saying that the requested feature MUST be done with const. However, it should behave like one, which is impossible with methods.
-- Richard "Fleshgrinder" Fussenegger