Perl-Apollo / Corinna

Corinna - Bring Modern OO to the Core of Perl
Artistic License 2.0
157 stars 19 forks source link

Open Questions: Uninitialized versus undefined #12

Closed Ovid closed 3 years ago

Ovid commented 4 years ago

Please leave feedback regarding question 1 on this ticket.

haarg commented 4 years ago

If there was an "unset" state, would the predicate method be the only way to check for that? Would a lazy attribute that hasn't been read be set or unset? Would clearing an attribute put it back to unset? Would a cleared attribute re-run its lazy initializer?

kroenlein commented 4 years ago

Yes for: if a slot (has $foo :predicate) can be undef, we probably want the predicate method to return true for has_foo

One of the glorious aspects of a Perl hash is the fact that exists can return a different thing than defined. Much as one could conceive of a C++ object as a struct with sugar, I always loved that previous object systems were hashes with sugar and have thus supported that subtle distinction. When I am shredding records in databases where column values could be null, I've often used this capability to model the distinction between There was a record and the value in the column was null verses There was no record.

duncand commented 3 years ago

When using blessed-hashref type OO, its easy to distinguish a non existing slot from an existing slot whose value is undef.

In the general case I see this issue not being specific to slots but it can also apply to regular my variables or parameters or anything else, and so a solution should be the same for all of them.

I propose that for Corrina version 1 there isn't any real value to distinguishing these cases and we should just formally say that there's no such thing as a non existing or unset slot.

We should simply say that slots always exist / are set and that they have undef as their value until something else is explicitly assigned to them. The exact same behaviour as my variables and such have.

Where there is any business case for the concept of not assigned yet, there are other ways to provide that.

In the normal sense, every slot has a meaningful value once at the latest the new/builder has run.

If logic in a class requires a concept of a slot that is only set some time after object creation, then they should manage that explicitly in some way, such as recognizing undef to mean not set yet, or having an extra companion slot that is a boolean which says whether the regular slot is considered set; generally you only need this if the regular slot allows undef as a normal value or if the normal slot has a type that doesn't allow undef at all (it might default to say zero or the empty string in that case whatever is appropriate for the type).

mscha commented 3 years ago

If there really is a need to be able to distinguish uninitialized slots from undefined slots, I don't think :predicate is the way to do that. Otherwise, how can you make this work?

class Company
{
    has $ceo :new :reader :predicate;

    method hire_ceo($person)
    {
        $ceo = $person;
    }

    method fire_ceo()
    {
        $ceo = undef;   # or uninitialized?  or uninitialize($ceo)?  delete($ceo)?
    }
}

my $c = Conpany->new();
say $c->has_ceo ? 'true' : 'false';    # false
$c->hire_ceo(ceo=>$john);
say $c->has_ceo ? 'true' : 'false';    # true
$c->fire_ceo();
say $c->has_ceo ? 'true' : 'false';    # should be false
Ovid commented 3 years ago

@mscha Sigh. Another nice idea shot down by logic.

I could try to argue a different case, but what you're suggesting matches people's expectations better. I'm unsure that the uninitialized versus undefined differentiation works well for Perl (at least, not for predicate methods).

rabbiveesh commented 3 years ago

If this is going into core, we could add another meaning to exists, where it would check if the value was initialized.

Right now there's no meaning to an exists check on a scalar, so it won't conflict with anything.

On Sun, Jun 20, 2021, 18:14 Ovid @.***> wrote:

@mscha https://github.com/mscha Sigh. Another nice idea shot down by logic.

I could try to argue a different case, but what you're suggesting matches people's expectations better. I'm unsure that the uninitialized versus undefined differentiation works well for Perl (at least, not for predicate methods).

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Ovid/Cor/issues/12#issuecomment-864569495, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFURPKWEFJ67EUX23LN3SHTTTYAVVANCNFSM4NC2XSCQ .

Ovid commented 3 years ago

@rabbiveesh That's a great idea and one, to be honest, that we've discussed in February (IRC, not here). Our current plan is to freeze everything we can and when/if Corinna goes into core, have ideas like this be proposed through the new RFC process for Perl.

duncand commented 3 years ago

@Ovid

A best practice is that an existing slot/variable/etc always contains a value of its declared type, which means there is no such thing as an existing slot that doesn't have a value. When one has a variable that conceptually doesn't always have a value, such as if it may contain undef, a good naming pattern is to prefix the name with maybe so for example a variable that always contains a Foo might be called foo but one that might be undefined might be called maybe_foo.

If we want to have the concept of a slot that doesn't always exist, I suggest the declaration syntax maybe_has as a complement to has. Declaring a slot with maybe_has allows the slot to not exist while declaring it with has means the slot must always exist. Or alternately have :maybe option for plain has which means the same thing.

Ovid commented 3 years ago

@duncand Agreed we need more work here, but this is one area I've gotten some pushback on and I'm not convinced what the right direction is. Since we can't (yet) have types, we can't (yet) fix this issue. However, if you see something we have to protect for future work, speak up. I'd hate for current design decisions to lock us out of good solutions later.

duncand commented 3 years ago

Agreed we need more work here, but this is one area I've gotten some pushback on and I'm not convinced what the right direction is. Since we can't (yet) have types, we can't (yet) fix this issue. However, if you see something we have to protect for future work, speak up. I'd hate for current design decisions to lock us out of good solutions later.

@Ovid In that case, I would say the best thing to do is that Corinna v1 explicitly does not provide any way for a user to ask if a slot has ever been assigned to or has been initialized.

A declared slot always exists. There is no such thing as a special runtime call to ask if a value exists. This isn't a hash where each key is determined at runtime. We already know exactly what all the slots are at compile time.

Every slot implicitly defaults to the single generic Perl undef until it is explicitly set.

For common use cases where undef is nominally not a normal value of the slot's type, simply testing it for the undefined value shows if it has been set or not.

For other use cases where undef is a valid "set" value, users can either keep a simple "maybe" type object in the slot to differentiate, or have a companion other slot that is a boolean to say if this one has been set or not.

Per other suggestions I've made (if slot setters return anything, they shouldn't), the best forwards-compatible solution is to simply not provide a feature at all, which lets you add it later if you want to, but meanwhile there won't be any code existing that relies on a feature you might want to change or take away.

Ovid commented 3 years ago

@duncand I'm leaning towards that, but people want :predicate, too. I don't blame them. But I hear what you're saying.

leonerd commented 3 years ago

@duncand That idea suits me; mostly because it's exactly what Object::Pad already does :)

duncand commented 3 years ago

I'm leaning towards that, but people want :predicate, too. I don't blame them. But I hear what you're saying.

@Ovid You can still have :predicate, but its just defined as returns false if the slot contains undef and true otherwise. Or for forwards compatibility maybe name it :defined instead as then it directly reflects this behaviour. For auto-generated stuff, far and away I think what normal consumers of the class, the public API, calling has_foo() or defined_foo() is just shorthand for !defined foo() or something like that. Those distinguishing set to undefined from not set at all seems to be a very edge case not needing a special feature for it.

Ovid commented 3 years ago

This is resolved for the MVP. Further issues should be new tickets.