Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open Questions: Uninitialized versus undefined #12

Closed
Ovid opened this issue May 16, 2020 · 14 comments
Closed

Open Questions: Uninitialized versus undefined #12

Ovid opened this issue May 16, 2020 · 14 comments
Labels
question Further information is requested

Comments

@Ovid
Copy link
Collaborator

Ovid commented May 16, 2020

Please leave feedback regarding question 1 on this ticket.

@Ovid Ovid changed the title Uninitialized versus undefined Open Questions: Uninitialized versus undefined May 16, 2020
@Ovid Ovid added the question Further information is requested label May 16, 2020
@haarg
Copy link

haarg commented May 16, 2020

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
Copy link

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
Copy link

duncand commented Feb 22, 2021

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
Copy link

mscha commented Jun 20, 2021

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
Copy link
Collaborator Author

Ovid commented Jun 20, 2021

@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
Copy link

rabbiveesh commented Jun 20, 2021 via email

@Ovid
Copy link
Collaborator Author

Ovid commented Jun 20, 2021

@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
Copy link

duncand commented Jun 20, 2021

@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
Copy link
Collaborator Author

Ovid commented Jun 21, 2021

@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
Copy link

duncand commented Jun 21, 2021

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
Copy link
Collaborator Author

Ovid commented Jun 21, 2021

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

@leonerd
Copy link
Collaborator

leonerd commented Jun 21, 2021

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

@duncand
Copy link

duncand commented Jun 21, 2021

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
Copy link
Collaborator Author

Ovid commented Aug 15, 2021

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

@Ovid Ovid closed this as completed Aug 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

7 participants