Welcome! Log In Create A New Profile

Advanced

[PHP-DEV] [RFC] [VOTE] Typed properties v2

Posted by Bob Weinand 
Bob Weinand
[PHP-DEV] [RFC] [VOTE] Typed properties v2
September 11, 2018 09:10AM
Hey,

As announced, we are starting the vote on typed properties today.

The voting period is two weeks, until sometime in the evening on Tuesday 25-09-2018.

Please find the RFC at https://wiki.php.net/rfc/typed_properties_v2.

Bob and Nikita
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 02:40PM
On Tue, 11 Sep 2018 at 08:05, Bob Weinand <[email protected]> wrote:

> Hey,
>
> As announced, we are starting the vote on typed properties today.
>
> The voting period is two weeks, until sometime in the evening on Tuesday
> 25-09-2018.
>
> Please find the RFC at https://wiki.php.net/rfc/typed_properties_v2.
>


For the record, I still think we will come to regret allowing non-nullable
type hints without any constraint on the author of the class to initialise
them correctly. As proposed, the invalid state may only be detected when it
causes a runtime error in completely unrelated code.

I gather that the authors of C# are currently going through a painful
development phase to introduce better support for non-nullable types, and
having to include many compromises and handle many edge-cases because it
was not done earlier. I realise this case is not completely comparable, but
we have an opportunity to get this right first time, and not just take the
easy option.

I am therefore going to make one last plea: if we don't yet know how to
assert that complex types are initialised, do not allow them to be
non-nullable in the first version of this feature.

That is, allow `class Foo { public ?Foo $foo = null; }`, but not `class Foo
{ public Foo $foo; }`.

This would still be a huge improvement to the language, but leaves us free
to design additional features to prevent Unitialized Property Errors
becoming as hated as Null Pointer Exceptions in Java or C#.

Regards,
--
Rowan Collins
[IMSoP]
Levi Morrison
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 06:00PM
On Wed, Sep 19, 2018 at 6:38 AM Rowan Collins <[email protected]> wrote:
>
> On Tue, 11 Sep 2018 at 08:05, Bob Weinand <[email protected]> wrote:
>
> > Hey,
> >
> > As announced, we are starting the vote on typed properties today.
> >
> > The voting period is two weeks, until sometime in the evening on Tuesday
> > 25-09-2018.
> >
> > Please find the RFC at https://wiki.php.net/rfc/typed_properties_v2.
> >
>
>
> For the record, I still think we will come to regret allowing non-nullable
> type hints without any constraint on the author of the class to initialise
> them correctly. As proposed, the invalid state may only be detected when it
> causes a runtime error in completely unrelated code.
>
> I gather that the authors of C# are currently going through a painful
> development phase to introduce better support for non-nullable types, and
> having to include many compromises and handle many edge-cases because it
> was not done earlier. I realise this case is not completely comparable, but
> we have an opportunity to get this right first time, and not just take the
> easy option.
>
> I am therefore going to make one last plea: if we don't yet know how to
> assert that complex types are initialised, do not allow them to be
> non-nullable in the first version of this feature.
>
> That is, allow `class Foo { public ?Foo $foo = null; }`, but not `class Foo
> { public Foo $foo; }`.
>
> This would still be a huge improvement to the language, but leaves us free
> to design additional features to prevent Unitialized Property Errors
> becoming as hated as Null Pointer Exceptions in Java or C#.
>
> Regards,
> --
> Rowan Collins
> [IMSoP]

I posit that this code:

class Foo {
public Foo $foo;
}

Is superior to this code:

class Foo {
public ?Foo $foo = null;
}

If after "initialization" that `$foo` is guaranteed to always contain
an object of type `Foo`. The reason is simple: for the actual lifetime
of the object the former correctly states that it will never be null,
while the latter opens up possibilities of it. That is, the former
provides *better* support for non-nullable types.

To prevent all forms of initialization errors we would have to do
analysis at the initialization site and prevent dynamic behaviors in
that region. For now I believe such things are better left to static
analysis tools than the engine.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 07:50PM
On Wed, 19 Sep 2018 at 16:57, Levi Morrison <[email protected]> wrote:

> I posit that this code:
>
> class Foo {
> public Foo $foo;
> }
>
> Is superior to this code:
>
> class Foo {
> public ?Foo $foo = null;
> }
>
> If after "initialization" that `$foo` is guaranteed to always contain
> an object of type `Foo`.



Yes, IF it guaranteed; but the current proposal offers no such guarantee.
Whichever of those definitions is used, the following code may fail:

function expectsFoo(Foo $foo) {
assert($foo->foo instanceOf Foo);
}



> The reason is simple: for the actual lifetime
> of the object the former correctly states that it will never be null,
> while the latter opens up possibilities of it.


It correctly states that it will never be null, but it incorrectly implies
that it will always be an instance of Foo.

It's not even true that *once initialised* the property will always contain
a Foo, because (if I understand it correctly) it is also allowed to call
unset() on a non-nullable property at any time.



> That is, the former
> provides *better* support for non-nullable types.
>

The property is only "non-nullable" because we have invented a new state,
very similar to null, and called it something other than "null".



> To prevent all forms of initialization errors we would have to do
> analysis at the initialization site and prevent dynamic behaviors in
> that region. For now I believe such things are better left to static
> analysis tools than the engine.
>

I agree that this is a hard problem, but I don't agree that this decision
is being made "for now". If we allow "non-nullable but uninitialized"
properties now, it will be extremely hard to change their behaviour in
future.

My request is emphatically not to reject the entire RFC until this is
solved. It is to say that "for now", all typed properties must be
initialised inline to a valid value; and by implication, that a
non-nullable object hint cannot be used.

Regards,
--
Rowan Collins
[IMSoP]
Rasmus Schultz
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 09:10PM
On Wed, Sep 19, 2018 at 7:43 PM Rowan Collins <[email protected]> wrote:

> I agree that this is a hard problem, but I don't agree that this decision
> is being made "for now". If we allow "non-nullable but uninitialized"
> properties now, it will be extremely hard to change their behaviour in
> future.

I'm with Rowan on this one.

This concept of "uninitialized" frankly seems like an allowance for
people who insist on writing poor code.

Nulls are bad, and "unintialized" is just another kind of "null" with
a built-in run-time type-check that executes on read - too late.

The first example given is just bad:

class Point {
public float $x, $y;

private function __construct() {}

public static function fromEuclidean(float $x, float $y) {
$point = new Point;
$point->x = $x;
$point->y = $y;
return $point;
}
}

You define two invariants: $x and $y must be floats - and then proceed
to break those constraints in the constructor?

Wrong. The RFC itself accurately states that "this code can be
rewritten to indirect through __construct() instead" - as shown in the
previous example.

Now why would you deliberately open the fences and knowingly invite
people to write poor code like this?

As for the second example:

class Point {
public float $x, $y;

public function __construct(float $x, float $y) {
$this->doSomething();
$this->x = $x;
$this->y = $y;
}
}

If doSomething() attempts to read an uninitialized property while the
constructor is still executing, throwing a helpful "uninitialized"
error is fine.

But, in my opinion, once the constructor has executed, the invariants
as declared by the class itself must be satisfied.

If there's one meaningful use-case for allowing objects in a
partially-initialized state, it's during
hydration/unserialization/reflection scenarios, maybe - but in those
cases, you're willfully bypassing the constructor; it's not the
everyday 95% use-case and some risk is acceptable here, you'll get
around it with tests. But nobody wants to write tests all day to see
if any classes contain "unininitialized" properties - that misses half
the whole point of being able to declare those types in the first
place, e.g. makes type-hinted private/protected properties totally
unreliable.

Once this is in a release, it'll be unfixable, and in my opinion will
likely go down in history as another one of those little things we
wish we could go back in time and fix :-/

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Levi Morrison
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 10:10PM
On Wed, Sep 19, 2018 at 1:06 PM Rasmus Schultz <[email protected]> wrote:
>
> On Wed, Sep 19, 2018 at 7:43 PM Rowan Collins <[email protected]> wrote:
>
> > I agree that this is a hard problem, but I don't agree that this decision
> > is being made "for now". If we allow "non-nullable but uninitialized"
> > properties now, it will be extremely hard to change their behaviour in
> > future.
>
> I'm with Rowan on this one.
>
> This concept of "uninitialized" frankly seems like an allowance for
> people who insist on writing poor code.
>
> Nulls are bad, and "unintialized" is just another kind of "null" with
> a built-in run-time type-check that executes on read - too late.
>
> The first example given is just bad:
>
> class Point {
> public float $x, $y;
>
> private function __construct() {}
>
> public static function fromEuclidean(float $x, float $y) {
> $point = new Point;
> $point->x = $x;
> $point->y = $y;
> return $point;
> }
> }
>
> You define two invariants: $x and $y must be floats - and then proceed
> to break those constraints in the constructor?
>
> Wrong. The RFC itself accurately states that "this code can be
> rewritten to indirect through __construct() instead" - as shown in the
> previous example.
>
> Now why would you deliberately open the fences and knowingly invite
> people to write poor code like this?
>
> As for the second example:
>
> class Point {
> public float $x, $y;
>
> public function __construct(float $x, float $y) {
> $this->doSomething();
> $this->x = $x;
> $this->y = $y;
> }
> }
>
> If doSomething() attempts to read an uninitialized property while the
> constructor is still executing, throwing a helpful "uninitialized"
> error is fine.
>
> But, in my opinion, once the constructor has executed, the invariants
> as declared by the class itself must be satisfied.
>
> If there's one meaningful use-case for allowing objects in a
> partially-initialized state, it's during
> hydration/unserialization/reflection scenarios, maybe - but in those
> cases, you're willfully bypassing the constructor; it's not the
> everyday 95% use-case and some risk is acceptable here, you'll get
> around it with tests. But nobody wants to write tests all day to see
> if any classes contain "unininitialized" properties - that misses half
> the whole point of being able to declare those types in the first
> place, e.g. makes type-hinted private/protected properties totally
> unreliable.
>
> Once this is in a release, it'll be unfixable, and in my opinion will
> likely go down in history as another one of those little things we
> wish we could go back in time and fix :-/
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: http://www.php.net/unsub.php

PHP permits skipping constructors. The code may not work if you do so,
but it's the state of how things are. Validating after a constructor
call will not catch all issues while requiring a constructor, whereas
I think this code should be allowed:

class User {
public int $id;
public string $preferred_name;
public string $username;
}

I doubt we will come to a resolution -- these points were already
pointed out in the discussion phase.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 11:20PM
On 19/09/2018 21:04, Levi Morrison wrote:
> I think this code should be allowed:
>
> class User {
> public int $id;
> public string $preferred_name;
> public string $username;
> }

Why? What contract is being enforced by that class that is not enforced
by this class?

class User {
public ?int $id=null;
public ?string $preferred_name=null;
public ?string $username=null;
}

Both require the consumer of the class to trust that someone, somewhere, has initialised the fields (and not subsequently unset them).

Or have I misunderstood what you intended with that example?

Regards,

--
Rowan Collins
[IMSoP]


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Marco Pivetta
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 11:40PM
On Wed, Sep 19, 2018 at 11:17 PM Rowan Collins <[email protected]>
wrote:

> On 19/09/2018 21:04, Levi Morrison wrote:
> > I think this code should be allowed:
> >
> > class User {
> > public int $id;
> > public string $preferred_name;
> > public string $username;
> > }
>
> Why? What contract is being enforced by that class that is not enforced
> by this class?
>
> class User {
> public ?int $id=null;
> public ?string $preferred_name=null;
> public ?string $username=null;
> }
>
> Both require the consumer of the class to trust that someone, somewhere,
> has initialised the fields (and not subsequently unset them).
>
> Or have I misunderstood what you intended with that example?
>
> Regards,
>
> --
> Rowan Collins
> [IMSoP]
>

At least the approach without nullable properties will lead to a Throwable
when a read is attempted on an uninitialized object, which is still better
than nullability checks all over the place.

And yes, constructing an object without going through its constructor is
quite common anyway - this was indeed part of the upfront discussion, and
is also part of why I gave a +1 to the current RFC.

Marco Pivetta

http://twitter.com/Ocramius

http://ocramius.github.com/
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 19, 2018 11:50PM
On 19/09/2018 22:30, Marco Pivetta wrote:
>
> At least the approach without nullable properties will lead to a
> Throwable when a read is attempted on an uninitialized object, which
> is still better than nullability checks all over the place.


Is it? Doesn't it just mean writing this:

try {
    someFunction($object->propertyThatClaimsToBeNonNullable);
} catch ( TypeError $e ) {
    ...
}

Instead of this:

if ( ! is_null($object->propertyThatClaimsToBeNonNullable) ) {
    someFunction($object->propertyThatClaimsToBeNonNullable);
} else {
    ...
}

For that matter, all I need to do is define someFunction as taking a
non-nullable parameter, and I get the TypeError either way.

Surely the point of a non-nullable property shouldn't be "it gives a
slightly different error if it's not set", it should be "you don't have
to worry about this not being set, because the language will enforce
that somewhere". (And to cover your last point, that somewhere doesn't
need to be the constructor, if requiring that is really such a big problem.)

Regards,

--
Rowan Collins
[IMSoP]


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Marco Pivetta
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 12:00AM
On Wed, Sep 19, 2018 at 11:46 PM Rowan Collins <[email protected]>
wrote:

> On 19/09/2018 22:30, Marco Pivetta wrote:
> >
> > At least the approach without nullable properties will lead to a
> > Throwable when a read is attempted on an uninitialized object, which
> > is still better than nullability checks all over the place.
>
>
> Is it? Doesn't it just mean writing this:
>
> try {
> someFunction($object->propertyThatClaimsToBeNonNullable);
> } catch ( TypeError $e ) {
> ...
> }
>
> Instead of this:
>
> if ( ! is_null($object->propertyThatClaimsToBeNonNullable) ) {
> someFunction($object->propertyThatClaimsToBeNonNullable);
> } else {
> ...
> }
>
> For that matter, all I need to do is define someFunction as taking a
> non-nullable parameter, and I get the TypeError either way.
>
> Surely the point of a non-nullable property shouldn't be "it gives a
> slightly different error if it's not set", it should be "you don't have
> to worry about this not being set, because the language will enforce
> that somewhere". (And to cover your last point, that somewhere doesn't
> need to be the constructor, if requiring that is really such a big
> problem.)
>
> Regards,
>
> --
> Rowan Collins
> [IMSoP]
>

That's what static analysis is for (see a bit above).

Sadly, static analysis doesn't really fit the engine out of the box, as PHP
is a bit too dynamic for that, and it doesn't really consider cross-file
declared symbols anyway.
Still, tools like PHPStan or Psalm can easily aid with that.

Also, there are scenarios (discussed in typed properties v1) that make the
uninitialized state actually favorable (every serializer ever, like every
one, really).

Marco Pivetta

http://twitter.com/Ocramius

http://ocramius.github.com/
Christian Stoller
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 08:10AM
> -----Ursprüngliche Nachricht-----
> Von: Rowan Collins
> Gesendet: Mittwoch, 19. September 2018 23:47
> An: PHP Internals List
> Betreff: Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
>
> On 19/09/2018 22:30, Marco Pivetta wrote:
> >
> > At least the approach without nullable properties will lead to a
> > Throwable when a read is attempted on an uninitialized object, which
> > is still better than nullability checks all over the place.
>
>
> Is it? Doesn't it just mean writing this:
>
> try {
> someFunction($object->propertyThatClaimsToBeNonNullable);
> } catch ( TypeError $e ) {
> ...
> }
>
> Instead of this:
>
> if ( ! is_null($object->propertyThatClaimsToBeNonNullable) ) {
> someFunction($object->propertyThatClaimsToBeNonNullable);
> } else {
> ...
> }
>
> For that matter, all I need to do is define someFunction as taking a
> non-nullable parameter, and I get the TypeError either way.
>
> Surely the point of a non-nullable property shouldn't be "it gives a
> slightly different error if it's not set", it should be "you don't have
> to worry about this not being set, because the language will enforce
> that somewhere". (And to cover your last point, that somewhere doesn't
> need to be the constructor, if requiring that is really such a big problem.)
>
> Regards,
>
> --
> Rowan Collins
> [IMSoP]
>

Just an idea: If an object is initialized without having all non-nullable properties initialized, one could store the instantiation place, and give this hint if an uninitialized property is accessed later.

Example:

// User.php

class User {
public Group $group;
}

// functions.php

function initUser() {
$user = new User(); // store this line and filename internally, because User::$group is uninitialized
return $user;
}

// index.php

$user = initUser();
echo $user->group->name; // Throws TypeError informing about access on uninitialized property and that it was initialized in file functions.php at line 3.


The TypeError still occurs but it makes it easy to find and fix the issue. And the same could be done with unsetting - save the place where an non-nullable property has been unsetted and inform the user where it happened if the unsetted property is accessed.

Best regards

Mit freundlichen Grüßen aus Paderborn

Christian Stoller
Web-Entwicklung

LEONEX Internet GmbH
Technologiepark 6
33100 Paderborn
Tel: +49 (5251) 4142-526
Fax: +49 (5251) 4142-501


HRB 8694 AG Paderborn
Geschäftsführer: Stephan Winter

________________________________
LEONEX ist umgezogen: Bitte beachten Sie unsere neue Adresse sowie unsere neuen Rufnummern.
Wann dürfen wir Sie in unsere neuen Räumlichkeiten einladen?
________________________________
Nikita Popov
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 01:00PM
On Wed, Sep 19, 2018 at 2:38 PM Rowan Collins <[email protected]>
wrote:

> On Tue, 11 Sep 2018 at 08:05, Bob Weinand <[email protected]> wrote:
>
> > Hey,
> >
> > As announced, we are starting the vote on typed properties today.
> >
> > The voting period is two weeks, until sometime in the evening on Tuesday
> > 25-09-2018.
> >
> > Please find the RFC at https://wiki.php.net/rfc/typed_properties_v2.
> >
>
>
> For the record, I still think we will come to regret allowing non-nullable
> type hints without any constraint on the author of the class to initialise
> them correctly. As proposed, the invalid state may only be detected when it
> causes a runtime error in completely unrelated code.
>
> I gather that the authors of C# are currently going through a painful
> development phase to introduce better support for non-nullable types, and
> having to include many compromises and handle many edge-cases because it
> was not done earlier. I realise this case is not completely comparable, but
> we have an opportunity to get this right first time, and not just take the
> easy option.
>
> I am therefore going to make one last plea: if we don't yet know how to
> assert that complex types are initialised, do not allow them to be
> non-nullable in the first version of this feature.
>
> That is, allow `class Foo { public ?Foo $foo = null; }`, but not `class Foo
> { public Foo $foo; }`.
>
> This would still be a huge improvement to the language, but leaves us free
> to design additional features to prevent Unitialized Property Errors
> becoming as hated as Null Pointer Exceptions in Java or C#.


I am sympathetic to the wish of reporting initialization errors during
construction rather than at the use-site. However, there is no clear
proposal on how this will be reconciled with other requirements that we
have, such as support of uninitialized properties for lazy initialization,
etc.

I do not consider it advisable to require a null initialization as a "first
iteration" of the proposal. Regardless of what our intention might be, the
effect of such a restriction will not be "I'm not going to type this
property for now, because non-nullable types are not supported", it's going
to be "I'll give this a nullable type even though it isn't, because that's
better than no type at all." Use of nullable types where they are not
necessary would be a disastrous outcome of this proposal.

To move this discussion forward in a productive direction, we need a
concrete, detailed proposal of how enforcement of initialization should
work, while being compatible with secondary requirements. I believe it goes
without saying that we cannot change the typed properties RFC in such a
substantive way at this point in time. However, if you or someone else can
bring forward an RFC that specifies the precise semantics of initialization
checks as you envision them and which is endorsed by maintainers of
libraries with special initialization requirements, then we still have a
lot of time to discuss and incorporate such a proposal before typed
properties ship in PHP 7.4.

Thanks,
Nikita
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 02:00PM
On Thu, 20 Sep 2018 at 11:58, Nikita Popov <[email protected]> wrote:

> I do not consider it advisable to require a null initialization as a
> "first iteration" of the proposal. Regardless of what our intention might
> be, the effect of such a restriction will not be "I'm not going to type
> this property for now, because non-nullable types are not supported", it's
> going to be "I'll give this a nullable type even though it isn't, because
> that's better than no type at all." Use of nullable types where they are
> not necessary would be a disastrous outcome of this proposal.
>

This, ultimately, is where we disagree. To me, the "non-nullable types"
this proposal provides are non-nullable in name only, and using them does
little more than adding a comment saying "I promise this won't be null".
Encouraging people to use them gives them a false guarantee, and allowing
them to do so prevents us adding a stricter version of the feature later.



> To move this discussion forward in a productive direction, we need a
> concrete, detailed proposal of how enforcement of initialization should
> work, while being compatible with secondary requirements.
>

It feels to me that many of the "secondary requirements" are actually
separate problems which should be seen as pre-requisites, rather than
constraints. The lazy initialization pattern seems to be a hack around lack
of better support for property accessors. The mention of serializers
needing custom methods of initialisation reminded me of the occasional
discussion of better replacements for Serializable and JsonSerializable.
And so on.

Fixing that list of pre-requisites would obviously take time, which is why
I wanted to buy that time by releasing initialised-only type hints first,
and working towards a greater goal.

However, I've probably beaten this drum long enough. I will hope to be
wrong, and that making things stricter will still be possible later.

Regards,
--
Rowan Collins
[IMSoP]
Lester Caine
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 02:30PM
On 19/09/2018 22:58, Marco Pivetta wrote:
> Also, there are scenarios (discussed in typed properties v1) that make the
> uninitialized state actually favorable (every serializer ever, like every
> one, really).

I still find a problem with this idea that everything must be
initialized. If I am working with business logic built into the database
then 'NULL' is very much a valid state and when I create an object
encapsulating a record it's values should be NULL until it is actually
download, or more normally until a new record has SOME of it's fields
populated. It is simply not sensible to be nailing down every variable
that is being passed and it is certainly not 'bad coding' to be working
with uninitialized data - it's handling just what needs to be
initialized that is the job of the code. And that is unlikely to be done
in the constructor!

--
Lester Caine - G8HFL
-----------------------------
Contact - https://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - https://lsces.co.uk
EnquirySolve - https://enquirysolve.com/
Model Engineers Digital Workshop - https://medw.co.uk
Rainbow Digital Media - https://rainbowdigitalmedia.co.uk

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Rasmus Schultz
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 04:50PM
On Wed, Sep 19, 2018 at 10:04 PM Levi Morrison <[email protected]> wrote:

> I think this code should be allowed:
>
> class User {
> public int $id;
> public string $preferred_name;
> public string $username;
> }

This code is broken - by making the properties non-nullable, you're
literally saying "these properties will be initialized", then
proceeding to not initialize them. That's just incomplete code.

Note that we're talking about two different things here - I was
talking about bypassing declared constructors for technical reasons.

You're talking about omitting constructors from the declaration - but
all classes have a constructor. Not declaring it just implies an empty
constructor. You can even invoke it using reflection.

For your use-case, assuming you insist on writing bad code, is more
accurate like this:

class User {
public ?int $id;
public ?string $preferred_name;
public ?string $username;
}

These properties are valid without a constructor. You can safely infer
them as null, rather than as "uninitialized".

Non-nullable properties aren't valid without initialization. Not in
any language I've ever heard of.

Maybe PHP has to be the first to prove every other language right?

We have enough half-baked features and inconsistencies as it is.

We'll regret this forever. Like how Javascript developers regret
having null and undefined on a daily basis.

Uninitialized is the new null - it's the PHP equivalent of undefined
in Javascript.

Please no.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Levi Morrison
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 05:00PM
This will be my last reply to this thread. Fundamentally:

class User {
public ?int $id;
public ?string $preferred_name;
public ?string $username;
}

^ This permits null properties at all times. This is acceptable
behavior if null is valid for the domain. It is not valid for this
domain -- all 3 are required.

class User {
public int $id;
public string $preferred_name;
public string $username;
}

^ This never permits null properties, and using them without
initializing them is an error, and you get notified by the runtime
that such a thing happened. This is good and desirable behavior.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Sara Golemon
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 05:30PM
On Thu, Sep 20, 2018 at 9:50 AM, Levi Morrison <[email protected]> wrote:
> This will be my last reply to this thread.
>
This will be my first and, God willing, only reply to this thread.

> Fundamentally:
>
> class User {
> public int $id;
> public string $preferred_name;
> public string $username;
> }
>
> ^ This never permits null properties, and using them without
> initializing them is an error, and you get notified by the runtime
> that such a thing happened. This is good and desirable behavior.
>
This is bad and undesirable behavior.

Deferring the error to on-read makes the properties magic and
unknowable. This is broken by design. The RFC got my vote because
broken never stood in PHP's way, and at the very least, as a library
author, I am empowered to aggressively initialize my properties even
if the runtime gives me insufficient protections from coding errors.

A static analysis engine can pick up the slack where the engine falls
short, so that's my yes vote, but it's not an endorsement of the
fundamentally broken design.

-Sara

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Pedro Magalhães
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 05:50PM
On Thu, Sep 20, 2018 at 12:50 PM Rowan Collins <[email protected]>
wrote:

> Encouraging people to use them gives them a false guarantee, and allowing
> them to do so prevents us adding a stricter version of the feature later.
>

What exactly would prevent us from enforcing it in the future? The way I
see it, whenever that would be possible, we could get rid of the
uninitialized state.

Regards,
Pedro
Larry Garfield
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 20, 2018 06:00PM
On Thursday, September 20, 2018 6:50:45 AM CDT Rowan Collins wrote:
> On Thu, 20 Sep 2018 at 11:58, Nikita Popov <[email protected]> wrote:
> > I do not consider it advisable to require a null initialization as a
> > "first iteration" of the proposal. Regardless of what our intention might
> > be, the effect of such a restriction will not be "I'm not going to type
> > this property for now, because non-nullable types are not supported", it's
> > going to be "I'll give this a nullable type even though it isn't, because
> > that's better than no type at all." Use of nullable types where they are
> > not necessary would be a disastrous outcome of this proposal.
>
> This, ultimately, is where we disagree. To me, the "non-nullable types"
> this proposal provides are non-nullable in name only, and using them does
> little more than adding a comment saying "I promise this won't be null".
> Encouraging people to use them gives them a false guarantee, and allowing
> them to do so prevents us adding a stricter version of the feature later.
>
> > To move this discussion forward in a productive direction, we need a
> > concrete, detailed proposal of how enforcement of initialization should
> > work, while being compatible with secondary requirements.
>
> It feels to me that many of the "secondary requirements" are actually
> separate problems which should be seen as pre-requisites, rather than
> constraints. The lazy initialization pattern seems to be a hack around lack
> of better support for property accessors. The mention of serializers
> needing custom methods of initialisation reminded me of the occasional
> discussion of better replacements for Serializable and JsonSerializable.
> And so on.
>
> Fixing that list of pre-requisites would obviously take time, which is why
> I wanted to buy that time by releasing initialised-only type hints first,
> and working towards a greater goal.
>
> However, I've probably beaten this drum long enough. I will hope to be
> wrong, and that making things stricter will still be possible later.
>
> Regards,

If I may...

I think the distinction here is that one group is arguing for "state of the
data assertions" while the RFC as implemented is "setter assertion shorthand".
That is, it doesn't assert that a value IS a given type, but that it can only
be SET TO a given type.

That naturally has some coverage holes. The question then being "is that
enough?"

I don't think a complete IS enforcement is possible given PHP's nature. We
don't enforce at compile time that you cannot call a function with an
incorrect type; that's enforced at runtime, or by a static analyzer. That is:

function test(int $a) {}

test($_GET['a']);

Cannot possibly be verified at compile time. This isn't a compile time check
either:

$b = 'foo';
test($b);

It's a runtime TypeError, not compile time.

That hasn't brought about the end of the world, so I'm not super worried about
imperfect property checks destroying everything.

That said, I totally appreciate the desire as a consumer of an object to know
that it's read-type-safe. I also get that there's many different pathways by
which an object could get initialized so there's no one clear validation
chokepoint.

But... could we have multiple?

To wit, could we add an engine check to scan an object and make sure its
objects are all type valid right-now (viz, nothing is unitialized), and then
call it on selected actions automatically and allow users to call it at
arbitrary times if they are doing more esoteric things?

I'm thinking:

* on __construct() exit.
* on __wakeup() exit.
* Possibly other similar checkpoints.
* When a user calls is_fully_initialized($obj); (or something)

is_fully_initialized() would return bool. The other checkpoints would throw a
TypeError.

That would layer on top of the current RFC cleanly, would give us the read-
assurance that we'd like in the 95% case, wouldn't require any new syntax
beyond a single function, and still lets serialization libraries do their
thing.

If needed, perhaps there's a way we can let a serialization tool disable that
check as an opt-out, and then the developer is just on-their-honor to get it
right. If it blows up later, well, file a bug with the serialization library.
In a dynamic runtime language I don't think we'll ever be able to do better
than that.

Would that be viable as a follow-up to the current RFC?

--Larry Garfield
Rasmus Schultz
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 10:00AM
On Thu, Sep 20, 2018 at 4:50 PM Levi Morrison <[email protected]> wrote:
>
> This will be my last reply to this thread. Fundamentally:
>
> class User {
> public ?int $id;
> public ?string $preferred_name;
> public ?string $username;
> }
>
> ^ This permits null properties at all times. This is acceptable
> behavior if null is valid for the domain. It is not valid for this
> domain -- all 3 are required.

If all three are required, your constructor needs to reflect that.

The reason constructors even exist in the first place is to initialize
the object's data members and establishing the invariant of the class.

Constructors are supposed to prepare objects for use.

Your (empty, implicit) constructor isn't doing that.

Your reasoning about this is circular - you want your properties to
be optionally initialized and non-null at the same time. You get
around this by coming up with a new term "undefined" for a
special kind of null that triggers run-time exceptions on read.

The whole idea is unnecessarily complex, and will certainly lead
to silent bugs that allow partially-initialized domain objects to
pass through many layers of a system without getting caught
until some code tries to read an "uninitialized" property.

function make_foo(): Foo {
return new Foo(); // missing a property
}

function x() { ... }
function y() { ... }
function z() { ... }

x(y(z(make_foo()));

The instance travels through layers and layers of callls and
fails somewhere in the x() or y() function because of a missing
value ... why was the value missing? why wasn't it initialized?
where was it supposed to have been initialized? where did
this bad instance of Foo even come from?

All that information is lost in a call-stack that's long gone by
the time you invoke x() and you'll have to backtrack through
layers and layers of code to try to figure out where this bad
instance came from.

This will be an everyday thing with code like that.

To say that we don't need to enforce invariants at the time of
construction is to say we don't need to enforce them at all -
enforcing them "maybe later" is the same as not enforcing
them. That is, when they trigger an error, it's no different
from a null-value triggering a similar error.

No matter how you twist it, uninitialized is the new null.

I'm fine with unintialized as an implementation detail that
ensures you can't read from properties while the constructor
is busy establishing the invariant.

I'm not at all fine with unintialized as a new language feature.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Lester Caine
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 10:30AM
On 21/09/2018 08:58, Rasmus Schultz wrote:
> No matter how you twist it, uninitialized is the new null.
>
> I'm fine with unintialized as an implementation detail that
> ensures you can't read from properties while the constructor
> is busy establishing the invariant.
>
> I'm not at all fine with unintialized as a new language feature.

Ignoring the debate on uninitialized/null ... not all objects ARE
invariant and there are very good reasons for not setting values for
everything, but it seems that these types of object are deemed to be
'bad coding' where in fact the simply have elements that yet to be
'initialized' if at all for this instance of the object. The constructor
simply creates those elements that need to exit and does nothing with
the holders for elements that have yet to be populated ... they stay null.

--
Lester Caine - G8HFL
-----------------------------
Contact - https://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - https://lsces.co.uk
EnquirySolve - https://enquirysolve.com/
Model Engineers Digital Workshop - https://medw.co.uk
Rainbow Digital Media - https://rainbowdigitalmedia.co.uk

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 11:30AM
On Thu, 20 Sep 2018 at 16:52, Larry Garfield <[email protected]> wrote:

> I think the distinction here is that one group is arguing for "state of
> the
> data assertions" while the RFC as implemented is "setter assertion
> shorthand".
> That is, it doesn't assert that a value IS a given type, but that it can
> only
> be SET TO a given type.
>
> I don't think a complete IS enforcement is possible given PHP's nature.
> ...
> That hasn't brought about the end of the world, so I'm not super worried
> about
> imperfect property checks destroying everything.
>
> That said, I totally appreciate the desire as a consumer of an object to
> know
> that it's read-type-safe.



This is a useful distinction, thank you for putting it so clearly.

Perhaps it depends whether you are looking at the feature as a library
author, or a library consumer: as an author, you want to protect "your"
objects from invalid *assignments*, inside and outside the library; as a
consumer, you want assurance that you can *use* the object in a particular
way. The current implementation provides a good tool for the author, but
falls short for the consumer.




> To wit, could we add an engine check to scan an object and make sure its
> objects are all type valid right-now (viz, nothing is unitialized), and
> then
> call it on selected actions automatically and allow users to call it at
> arbitrary times if they are doing more esoteric things?
>
> I'm thinking:
>
> * on __construct() exit.
> * on __wakeup() exit.
> * Possibly other similar checkpoints.
> * When a user calls is_fully_initialized($obj); (or something)
>
> is_fully_initialized() would return bool. The other checkpoints would
> throw a
> TypeError.
>

> That would layer on top of the current RFC cleanly, would give us the read-
> assurance that we'd like in the 95% case


I think this is a really sensible approach: as you say, it may never be
possible to assert the invariant in every case, so checking the most common
scenarios may be the pragmatic thing to do.

Conveniently, treating it as an occasional assertion, rather than a strict
invariant, means we can keep the unset() lazy-initialization hack; it's
still an odd feature IMO, but probably not all that likely to be triggered
by mistake.

Regards,
--
Rowan Collins
[IMSoP]
Larry Garfield
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 04:20PM
On Friday, September 21, 2018 4:20:20 AM CDT Rowan Collins wrote:
> On Thu, 20 Sep 2018 at 16:52, Larry Garfield <[email protected]> wrote:
> > I think the distinction here is that one group is arguing for "state of
> > the
> > data assertions" while the RFC as implemented is "setter assertion
> > shorthand".
> > That is, it doesn't assert that a value IS a given type, but that it can
> > only
> > be SET TO a given type.
> >
> > I don't think a complete IS enforcement is possible given PHP's nature.
> > ...
> > That hasn't brought about the end of the world, so I'm not super worried
> > about
> > imperfect property checks destroying everything.
> >
> > That said, I totally appreciate the desire as a consumer of an object to
> > know
> > that it's read-type-safe.
>
> This is a useful distinction, thank you for putting it so clearly.
>
> Perhaps it depends whether you are looking at the feature as a library
> author, or a library consumer: as an author, you want to protect "your"
> objects from invalid *assignments*, inside and outside the library; as a
> consumer, you want assurance that you can *use* the object in a particular
> way. The current implementation provides a good tool for the author, but
> falls short for the consumer.

Perhaps another disconnect here is that, in practice, the consumer of an
object property is, in my experience, almost always "me". I almost never have
public properties on my objects. On the rare occasion I do, it's for a
"struct object" I'm using internally as a more self-documenting and memory
efficient alternative to a nested associative array.

In either case, if I fail to initialize a variable the only code that would be
impacted is... my own, usually in the same class (or occasionally in a
subclass). Finding the bug then is pretty straightforward, and it's my own
damned fault, but therefore easy to fix.

In my experience at least, most of the modern code I see in the wild is the
same way. That means the potential impact of stray undefined properties
roaming around the code base is really small. Even vaguely reasonably
structured code already avoids this problem. I can't think of anything I've
written in the last few years that wouldn't work this way, even with a
constructor-exit-validation check, without any modification at all.

Naturally if someone is using a lot of public properties in their code the
potential for "undefined" bugs increases. That seems rather uncommon in my
world, though.

> > To wit, could we add an engine check to scan an object and make sure its
> > objects are all type valid right-now (viz, nothing is unitialized), and
> > then
> > call it on selected actions automatically and allow users to call it at
> > arbitrary times if they are doing more esoteric things?
> >
> > I'm thinking:
> >
> > * on __construct() exit.
> > * on __wakeup() exit.
> > * Possibly other similar checkpoints.
> > * When a user calls is_fully_initialized($obj); (or something)
> >
> > is_fully_initialized() would return bool. The other checkpoints would
> > throw a
> > TypeError.
> >
> >
> > That would layer on top of the current RFC cleanly, would give us the
> > read-
> > assurance that we'd like in the 95% case
>
> I think this is a really sensible approach: as you say, it may never be
> possible to assert the invariant in every case, so checking the most common
> scenarios may be the pragmatic thing to do.
>
> Conveniently, treating it as an occasional assertion, rather than a strict
> invariant, means we can keep the unset() lazy-initialization hack; it's
> still an odd feature IMO, but probably not all that likely to be triggered
> by mistake.
>
> Regards,

Another benefit, since it would, I think, boil down to a series of isset()
calls implemented in C land the performance impact is probably not measurable.
(Obviously we'd need to measure that... :-) )

--Larry Garfield
Rowan Collins
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 05:00PM
On Fri, 21 Sep 2018 at 15:11, Larry Garfield <[email protected]> wrote:

> Perhaps another disconnect here is that, in practice, the consumer of an
> object property is, in my experience, almost always "me". I almost never
> have
> public properties on my objects. On the rare occasion I do, it's for a
> "struct object" I'm using internally as a more self-documenting and memory
> efficient alternative to a nested associative array.


My impression is that people will be encouraged by this feature to
implement more value objects with public properties, where they currently
have getters and setters doing nothing but type checks; and thus more code
will be exposed to uninitialized properties if there is a bug in a
constructor.

Indeed, it's arguable that if all properties were private, there would be
no need to enforce type hints all, since analysis of where they were set
would be trivial.

Regards,
--
Rowan Collins
[IMSoP]
Rasmus Schultz
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 05:30PM
On Fri, Sep 21, 2018 at 10:24 AM Lester Caine <[email protected]> wrote:

> Ignoring the debate on uninitialized/null ... not all objects ARE
> invariant

hence nullable types.

> and there are very good reasons for not setting values for
> everything, but it seems that these types of object are deemed to be
> 'bad coding' where in fact the simply have elements that yet to be
> 'initialized' if at all for this instance of the object.

that's what nullable types are for.

> The constructor
> simply creates those elements that need to exit and does nothing with
> the holders for elements that have yet to be populated ... they stay null.

hence nullable types.

the point of using type-hints on the properties of a class is to describe
the invariant state of that class - if the state of an instance of that class
is not what the class itself prescribed/promised at the time when you
return from the constructor, what's the point?

the only difference between using nullable vs non-nullable property
type-hints then, is whether you can set them *back* to null after setting
them to value - but it's okay for them to return from the constructor in
a "null-like" state?

this doesn't provide *any* additional guarantees:

class Foo {
public int $bar;
}

$foo = new Foo(); // invalid state allowed

$foo->bar = 123; // valid state

$foo->bar = null; // invalid state NOT allowed?!

Have the effects on the null coalesce operator even been considered?

$bar = $foo->bar ?? "what";

Is unintialized "null-like enough" or will this trigger an error?

Extremely confusing.

Type-annotations are essentially assertions about the state of a program -
if you can't count on those assertions to be fulfilled (which you
can't if they're
not checked at the time of initialization) then they're not useful.

The bottom line for me is that property type-hints were supposed to make
my programs more predictable, make the language more reliable.

Instead, we've introduced a whole new kind of uncertainties, new ways to
write unpredictable code, a new kind of null that makes the language even
more unreliable.

In terms of reliability, it's actually sub-par to hand-written accessors.

Shorter syntax, reflection support, great - but ultimately at the cost of
reliability and predictable state.

For one, what good is reflection, if it tells me I can expect a property to be
an integer, and it turns out it doesn't have a value after all?

If you want horrible code with no guarantees, there are plenty of ways to
do that already - you don't need property type-hints for anything more than
mere convenience in the first place.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Christoph M. Becker
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 06:00PM
On 21.09.2018 at 17:25, Rasmus Schultz wrote:

> this doesn't provide *any* additional guarantees:
>
> class Foo {
> public int $bar;
> }
>
> $foo = new Foo(); // invalid state allowed
>
> $foo->bar = 123; // valid state
>
> $foo->bar = null; // invalid state NOT allowed?!
>
> [snip]
>
> In terms of reliability, it's actually sub-par to hand-written accessors.

You have no guarantee that a declared public property even exists
(regardless of the Typed Properties 2.0 RFC), because it might have been
unset. And no, the property is not really NULL in this case, but rather
undefined.

--
Christoph M. Becker

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Larry Garfield
Re: [PHP-DEV] [RFC] [VOTE] Typed properties v2
September 21, 2018 11:10PM
On Friday, September 21, 2018 10:25:50 AM CDT Rasmus Schultz wrote:
> On Fri, Sep 21, 2018 at 10:24 AM Lester Caine <[email protected]> wrote:
> > Ignoring the debate on uninitialized/null ... not all objects ARE
> > invariant
>
> hence nullable types.
>
> > and there are very good reasons for not setting values for
> > everything, but it seems that these types of object are deemed to be
> > 'bad coding' where in fact the simply have elements that yet to be
> > 'initialized' if at all for this instance of the object.
>
> that's what nullable types are for.
>
> > The constructor
> > simply creates those elements that need to exit and does nothing with
> > the holders for elements that have yet to be populated ... they stay null.
>
> hence nullable types.
>
> the point of using type-hints on the properties of a class is to describe
> the invariant state of that class - if the state of an instance of that
> class is not what the class itself prescribed/promised at the time when you
> return from the constructor, what's the point?
>
> the only difference between using nullable vs non-nullable property
> type-hints then, is whether you can set them *back* to null after setting
> them to value - but it's okay for them to return from the constructor in
> a "null-like" state?
>
> this doesn't provide *any* additional guarantees:
>
> class Foo {
> public int $bar;
> }
>
> $foo = new Foo(); // invalid state allowed
>
> $foo->bar = 123; // valid state
>
> $foo->bar = null; // invalid state NOT allowed?!
>
> Have the effects on the null coalesce operator even been considered?
>
> $bar = $foo->bar ?? "what";
>
> Is unintialized "null-like enough" or will this trigger an error?
>
> Extremely confusing.
>
> Type-annotations are essentially assertions about the state of a program -
> if you can't count on those assertions to be fulfilled (which you
> can't if they're
> not checked at the time of initialization) then they're not useful.
>
> The bottom line for me is that property type-hints were supposed to make
> my programs more predictable, make the language more reliable.
>
> Instead, we've introduced a whole new kind of uncertainties, new ways to
> write unpredictable code, a new kind of null that makes the language even
> more unreliable.
>
> In terms of reliability, it's actually sub-par to hand-written accessors.
>
> Shorter syntax, reflection support, great - but ultimately at the cost of
> reliability and predictable state.
>
> For one, what good is reflection, if it tells me I can expect a property to
> be an integer, and it turns out it doesn't have a value after all?
>
> If you want horrible code with no guarantees, there are plenty of ways to
> do that already - you don't need property type-hints for anything more than
> mere convenience in the first place.

Rasmus, would the "checkpoint validity checks" I suggested in another branch
of this thread ameliorate your concerns, at least to some degree? I think
they're as good as we'd be able to get, given the nature of PHP, but should at
least cover the most common cases.

--Larry Garfield
Sorry, only registered users may post in this forum.

Click here to login