I apologize for the long posts, but Larry asked me to comment on this.

On Thu, Sep 20, 2018 at 5:52 PM Larry Garfield <la...@garfieldtech.com> 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".

The point of the setter assertions is to provide guarantees about the state of
the data - there is literally no difference.

> 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 is literally the same thing - if it can only be set to a given
type, is can only
BE a given type.

The problem here is you want to make exemptions for null as type - as though
nulls aren't types (which they are) and as though nullable types aren't distinct
from the types they're derived from. (again, they are.)

> I don't think a complete IS enforcement is possible given PHP's nature.

I don't think that, and I don't expect that - I'm not suggesting we enforce
anything statically, I'm merely suggesting that a constructor needs to satisfy
the constraints specified by the class.

If you've type-hinted a property as int, you don't allow strings - that would be
pointless, right?

But that is precisely the problem we're talking about - if you've type-hinted a
property as non-nullable int, it shouldn't allow null... but that is
literally the
exemption you want to allow for - you just want to annotate the property
with an additional meta-data property "unintialized", which is literally the
same as adding a boolean "is set" for every property in your model.

> This isn't a compile time check
> either:
>
> $b = 'foo';
> test($b);
>
> It's a runtime TypeError, not compile time.

This actually illustrates my point nicely - the variable $b in this
case has nothing
to do with the state of the parameter var inside the function body,
until you pass
it via a function call. That's perfectly fine. You'll get an error at
the point where
the parameter type-constraint was violated - you can look at a stack-trace and
debug that easily.

Now take your example and call a constructor instead of a function:

class Test {
    public string $b;
}

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

Your constructor call generates no run-time error here, and the program
continues to execute with $test in a state that, according to specification of
the class itself, isn't valid.

Since the constructor is how a valid instance of Test gets generated in the
first place, you've missed your *only* chance to enforce those constraints -
meaning, you've missed the only opportunity you had to verify that the
constructor does in deed generate an instance of Test that lives up to
Test's own specification of what a Test instance is.

> 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?

In my opinion, this is a solution to the problem we created when we decided
every property should internally be annotated with an "initialized" boolean
property - you have all this extra state that wasn't part of the specification
of the class itself, and now you have to deal with that state.

In my opinion, tracking this extra state *during the constructor call* is
acceptable and necessary in a scripting language like PHP - I never
asked for static type-checking, I'm merely asking for this check to be
built-into the language and performed at run-time when you exit the
constructor, e.g. at the last moment where you can feasibly perform
this check.

If you don't perform that check, you have no guarantees at all.

Consider this example:

function login(int $user_id) {
  // ...
}

Inside the body of this function, you can safely assume you have an integer,
right? A basic guarantee provided by the language.

Now consider this alternative:

function login(User $user) {
  // ...
}

Inside the body of this function, you'd assume you have a valid instance
of User, right? The same basic guarantee provided by the language.

Wrong. Something might be "unintialized". This might in fact not be a valid
instance of User, it might be only partially initialized.

This is a problem, because I want my type User to be a reliable type - as
reliable as any other type.

Integers for example is nice, because I know I'm going to be able to add,
subtract, multiple, divide, and so on - if you've type-hinted a parameter as
int, you know it has those abilities, you know the + operator isn't going to
fail because there's a special kind of int that doesn't work with the
+ operator.

We want those same guarantees from our own types, that's all.

In the case where a type *doesn't* provide such a guarantee, it can explicitly
state that something is nullable - not guaranteed to be set. You have that
option already, you don't need unintialized properties for that.

If there really is a use-case for "optional values that must not
be removed again once they've been set" - and I can't think of one - but
if there is, you can handle that rare case with logic in getters/setters.

The normal everyday use-case is you want to know if a property is going
to be set or not - hence nullable property-types, which are part of the RFC.

Userland work-arounds for basic guarantees that the language should be
providing, in my opinion, are totally unacceptable - in a nutshell, because
basic guarantees provided for other types won't apply to objects.

It's incoherent with the workings of type-hints in the language, and will
make those substantially less useful.

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to