problem-solving icon indicating copy to clipboard operation
problem-solving copied to clipboard

Semi-predicate situation with `is required` and defaults on native attributes

Open jnthn opened this issue 4 years ago • 5 comments

Example for is required:

class C {
    has int $.x is required;
}
say C.new(x => 1); # C.new(x => 1)
say C.new(x => 0); # Dies: The attribute '$!x' is required, but you did not provide a value for it.

Example for defaults:

class C {
    has int $.x = -1
}
say C.new(x => 1); # C.new(x => 1)
say C.new(x => 0); # C.new(x => -1)

I noticed this while working on elimination of the mechanism currently used for required/defaults, and have preserved current semantics in that PR (because I'm primarily working on it to save myself some epic headaches when working on upcoming escape analysis improvements, and also for a smaller performance and JITted code size win on attribute access).

Natively typed attributes are represented in memory as the value, stored directly in the memory allocated for the object body. That's it. No Scalar container we can hang a descriptor off or use nullness of as a sentinel for "not initialized", for example, unlike with object attributes. Thus currently the default value and is required features go on the value itself, resulting in - taking int as an example - a situation where 0 indicates both "unassigned" but is itself a valid value. This feels worth a review and decision about what we want.

jnthn avatar Nov 11 '21 15:11 jnthn

Some reasonable (in that I can argue for each of them) options that come to mind:

  1. Deprecate is required and defaults on attribute values in 6.e, on the basis that we'd prefer language users don't get caught out by this behavior. It seems use of is required on native attributes in modules is very rare. Defaults are far more common, so this would hurt a bit.
  2. Leave it alone, on the basis that natively typed attributes are relatively rarely used anyway, that native types always have edges, and/or that the current semantics are the only ones that a language user who understands what natively typed attributes in Raku are could expect anyway.
  3. Only support them in the case where there is no user-defined BUILD, in which case we can determine whether it was missing by looking at the incoming construction parameters (it's thanks to BUILD and anything it calls being able to assign to attributes that we need a means to determine if they were assigned). This makes for a more complicated edge case, but perhaps nicely blends non-regression of existing code and usefulness with an avoidance of exposing a feature vulnerable to the semi-predicate issue. It's a mild extra complication in build plan handling.

An unreasonable option I'll argue against: try to represent native types differently if the have an is required or a default, e.g. adding an extra, secret, attribute that we also assign to indicate it was set. If language users are using native types it's probably for better control over memory size, so sneaking in more memory usage is unhelpful. Further, it'd be a nuisance to implement even inefficiently.

jnthn avatar Nov 11 '21 16:11 jnthn

Deprecate is required and defaults on attribute values in 6.e

I assume ^^^ was a typo for "on natively typed attribute values"? On that assumption, I can see merits in all three options but might opt for an option 1.5 – namely, make is required with native types a warning in 6e.

One reason a warning seems preferable to a ban is that, due to the limited expressiveness of native types, sometimes a zero value is signalling an error/missing value in user code as well (or, said differently, user code also faces the semi-predicate problem). It would be a shame for a user to have code where they'd really like to require a non-zero int and have Raku not let them require an int because it'd give them exactly what they want!

codesections avatar Nov 11 '21 16:11 codesections

I assume ^^^ was a typo for "on natively typed attribute values"?

Yes, everything suggested is only about natively typed attributes.

On that assumption, I can see merits in all three options but might opt for an option 1.5 – namely, make is required with native types a warning in 6e.

And presumably to defaults (has int $.x = 0;) also, given it's the same situation?

One reason a warning seems preferable to a ban is that, due to the limited expressiveness of native types, sometimes a zero value is signalling an error/missing value in user code as well (or, said differently, user code also faces the semi-predicate problem). It would be a shame for a user to have code where they'd really like to require a non-zero int and have Raku not let them require an int because it'd give them exactly what they want!

I wonder if we could let the language user suppress the warning by providing an is default to specify the value that they consider to be the error/missing value. So has int $.x is required is default(-1); would have the value set to be -1 right at object creation time, and use that as the basis for comparison of whether the attribute was assigned or not. Effectively, allowing the user to specify a value that they consider not vulnerable to the semi-predicate issue.

jnthn avatar Nov 11 '21 16:11 jnthn

I'm not sure is required on a native attribute should do anything ordinarily. The absence of any bits is a value in and of itself for these types, and attempting to type them to have a definiteness of sorts blurs the lines between them and their boxings. Doing nothing allows the trait to serve as a hint to the reader when values are being forwarded from new. If the absence of a value is desired, the boxing is desired, so a warning would stiil be OK, but moreso in cases where the boxing would be more appropriate inferrably, e.g. a forwarded object with an insufficient typing.

Kaiepi avatar Nov 11 '21 17:11 Kaiepi

Is native attributes a thing used so often we should try to huffmanize / provide convenience for? One could always just do:

class B {
    has int $.foo is built(False);
    submethod TWEAK(int :$foo!) { $!foo = $foo }
}

patrickbkr avatar Nov 11 '21 19:11 patrickbkr