On Sat, Jul 14, 2018 at 3:09 PM, Rowan Collins <rowan.coll...@gmail.com> wrote:
> Hi all, > > As briefly mentioned, I think the approach to non-nullable types in the > typed properties proposal needs more discussion, to make sure we're not > repeating Tony Hoare's "billion-dollar mistake". > > For parameter hints, nullability is naturally optional, particularly in > PHP where "nullable Foo" is equivalent to a union type "null or Foo". For > properties, this isn't the case, because every property must have some > initial state. > > The simplest solution, which I think would be a sensible starting point if > we're not ready for more complex changes, is to say that all typed > properties must have a default value, and if that default value is null, > the type hint must naturally be nullable. In other words, make "public Foo > $foo" illegal, but allow "public ?Foo $foo=null". > > The current RFC proposes the next simplest solution, which is to allow > non-nullable types, and trust the user to initialise them before use. > > My first objection to this is that it creates an entirely new state for > object properties to be in, which behaves differently from everything else > in the language. There will then be three or four different ways for an > object property to be "unset": > > - not declared at all; access gives a notice and the implicit value null > - declared with no default, uninitialised, implicitly null > - declared with a default of null, or explicitly assigned as null > - declared with a non-nullable type, never initialised; rather than an > implicit null, access generates an Error > > The bigger problem is that the properties are non-nullable in name only, > and the declaration can't be trusted. Consider the following class: > > class Validity { > public \DateTimeInterface $validFrom; > public ?\DateTimeInterface $validTo = null; > } > > If I have an instance $v of that class, I would expect to be able to > safely call $v->validFrom->getTimestamp(), but need to check for nulls > before doing the same with validTo. Under the current proposal, doing so > will risk errors, so I will have to check both properties before access > anyway. Worse, using is_null() or ?: will attempt to retrieve the value, so > I actually have to be *more* careful, and use isset() or ?? instead. > I'm just going to reply to this one line, because this is the root of your misunderstanding: The user should never, ever, under any circumstances (*) check for the initialization of a typed property. The user simply uses the property under the firm knowledge that it will be initialized -- or there is a bug in the initialization code. If there is a bug in the initialization code then it needs to be fixed there, not by introducing a check when working with the object. This is very, very different from a nullable property, where it is the users responsibility to handle the null value. If the user receives a null value it does *not* indicate an initialization bug and they *must* write code to account for it. Please do also remember that uninitialized properties and unset properties are the same thing, and during the last proposal there was a strong consensus that we must retain support for property unsetting to be compatible with Doctrine and other libraries using proxy objects. Nikita (*) Okay, there are exceptions, e.g. introspection libraries ;) > > Alternatively, I can trust the author of the class, but at that point they > might as well just use a docblock - the type hint documents their intent, > but is not enforced. > > > Swift is often cited as a language which gets nullability right, and a lot > of attention is given to Options, and the compiler ensuring that the None / > null case is handled; but at least as important is how it handles > initialisation of non-nullable properties, using "two-phase > initialisation": https://docs.swift.org/swift-b > ook/LanguageGuide/Initialization.html > > In short, Swift defines a specific point after which all properties must > have a valid value; before this point, the entire object is invalid for > read operations, so there is no need for a specific error when accessing > uninitialised properties. > > Swift marks this point by the call to the parent initialiser, ultimately > going up the chain to the root object. PHP has no root object, and no > mandatory parent constructor calls, so would need a different way to mark > this stage. Constructors can already violate the rules the first phase > should impose, so we can't just use the end of the constructor as the > validation point. > > One possibility would be to add a new keyword like "initialize" which must > be added to constructors in the presence of non-nullable properties. Above > that keyword, use of $this other than to write to its properties would be > an error; afterwards, the constructor could carry on as normal. > > > As I say, this is complex, and it may be best to add nullable typed > properties first, and give more time to try out approaches to > initialisation. > > Regards, > -- > Rowan Collins > [IMSoP]