<div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div>When I read the draft JEP "Null-Restricted and Nullable Types" a few things really clicked in my head.</div><div><br></div><div>The first is that when reviewing Java code, it is very common to need to convince oneself that some variable != null in order to understand the code or prove its correctness. So the addition of a "!" right in front of its declaration will be a huge, everyday win. I look forward to adding this feature to the growing list of other "How can the compiler help me prove this code is correct?" features like: generics, exhaustive switch cases, sealed types, etc.</div><div><br></div><div>But null-restriction is just one specific example of a more general concept, let's call it "ad hoc type restriction".</div><div><br></div><div>A whole lot of Java code is written using a strategy of "I have some value which can be modeled by type X, but only a subset Y of values of type X are valid for my particular use case, so I'm going to pass these values around as parameters of type X and then either validate them everywhere one can enter from the outside world... which, um, requires me to keep track of which values have come from the outside world and which have not, hmm..."</div><br><div>A null-restricted reference type is just the most common example of this - where the subset Y = X \ { null }.</div><br></div><div dir="ltr"><div>Other examples...</div><div><ul><li>Using int for: The size of some collection
(can't be negative)</li><li>Using byte or char for: An ASCII character</li><li>Using short for: A Unicode basic plane character (2FE0..2FEF are unassigned)</li><li>Using String for: SQL query, phone number, SSN,
etc. (must be non-null and have the proper syntax)</li></ul></div><div><div>But regardless of what X or Y is, it's very common for
the validation step to be missed or forgotten somewhere, leading to
bugs. One might even argue that a <i>majority</i> of bugs are due to incomplete validation of some sort or another. Keeping track of when validation is required and manually adding it in all those places is tedious and error prone.</div></div><div><br></div><div>OK, how could the compiler help me? I am starting with some type T, and I want to derive from it some new type R which only permits a subset of T's values. I want the compiler to guarantee this without too much performance penalty. I want R to be arbitrary - however I define it.</div></div><div dir="ltr"><div><br></div><div>Today I can "homebrew" type restriction on reference types by subclassing or wrapping T, for example:</div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">public class PhoneNumber {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> private String </span><span style="font-family:monospace">e164</span><span style="font-family:monospace">; // non-null and in E.164 format, e.g., "+15105551212"</span></div><div style="margin-left:40px"><span style="font-family:monospace"> public PhoneNumber(String s) {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> ... parse, validate E.164 </span><span style="font-family:monospace">syntax</span><span style="font-family:monospace">, normalize, ...</span></div><div style="margin-left:40px"><span style="font-family:monospace"> }</span></div><div style="margin-left:40px"><span style="font-family:monospace"> public String toString() {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> return this.e164;</span></div><div style="margin-left:40px"><span style="font-family:monospace"> }</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div><br></div><div>But doing that is not ideal because:</div><div><ol><li><span style="font-family:arial,sans-serif">This doesn't work for primitive types </span>(e.g., a person's age, grade point average, number of children, etc.)</li><li>A wrapper class loses all of the functionality that comes with the wrapped class. For example, I can't say<span style="font-family:monospace"> pn1.startsWith(pn2) </span><span style="font-family:arial,sans-serif">even though both values are essentially just </span><span style="font-family:monospace">String</span><span style="font-family:arial,sans-serif">s.</span></li><li>There is performance overhead</li></ol></div><div>[Side note - in Valhalla at least problem #3 goes away if the wrapper class is a value class?]</div><div><div><br></div><div>So I pose this question to the group: How could the language and compiler take the null-restricted types idea and fully generalize it so that:</div></div><div><ul><li>It's easy to define custom type restrictions on both primitive and reference types</li><li>Runtime performance is "as good as casting", i.e., validation only occurs when casting</li></ul></div><div>Here's one idea just for concreteness: Use special type restriction annotations, e.g.:</div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">@TypeRestriction(baseType = String.class, enforcer = PhoneNumberEnforcer.class)</span></div><div style="margin-left:40px"><span style="font-family:monospace">public @interface PhoneNumber {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> boolean </span><span style="font-family:monospace">requireNorthAmerican</span><span style="font-family:monospace">() default false;</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">public class PhoneNumberEnforcer implements TypeRestrictionEnforcer<String> {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> <a class="gmail_plusreply" id="plusReplyChip-2">@Override</a></span></div><div style="margin-left:40px"><span style="font-family:monospace"> public void validate(PhoneNumber restriction, String value) {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> if (value == null || !value.matches("\+[1-9][2-9][0-9]{6,14}"))</span></div><div style="margin-left:40px"><span style="font-family:monospace"> throw new </span><span style="font-family:monospace">InvalidPhoneNumberException</span><span style="font-family:monospace">("not in E.164 format");</span></div><div style="margin-left:40px"><span style="font-family:monospace"> if (restriction.</span><span style="font-family:monospace"></span><span style="font-family:monospace">requireNorthAmerican</span><span style="font-family:monospace"></span><span style="font-family:monospace">() && !value.charAt(0) != '1')</span></div><div style="margin-left:40px"><span style="font-family:monospace"> throw new InvalidPhoneNumberException("North American number required");</span></div><div style="margin-left:40px"><span style="font-family:monospace"> }</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">// Some random API...</span></div><div style="margin-left:40px"><span style="font-family:monospace">public void dial(@PhoneNumber String number) { ... }</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">// Some random code...</span></div><div style="margin-left:40px"><span style="font-family:monospace">String input = getUserInput();</span></div><div style="margin-left:40px"><span style="font-family:monospace">try {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> dialer.dial((@PhoneNumber String)input);</span></div><div style="margin-left:40px"><span style="font-family:monospace">} catch (InvalidPhoneNumberException e) {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> System.err.println("Invalid phone number: \"" + input + "\"");</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div style="margin-left:40px"><span style="font-family:monospace">String input2 = getUserInput();</span></div><div style="margin-left:40px"><span style="font-family:monospace">dialer.dial(input2); // "warning: implicit cast to </span><span style="font-family:monospace">@PhoneNumber String" ?</span></div><div><br></div><div>The compiler would insert bytecode or method call-outs at the appropriate points to guarantee that any variable with type <span style="font-family:monospace">@PhoneNumber String</span> would always contain a value that has successfully survived <span style="font-family:monospace">PhoneNumberEnforcer.validate()</span>. In other words, it would provide the same level of guarantee as <span style="font-family:monospace">String!</span> does, but it would be checking my custom type constraint instead.</div><div><br></div><div>The Checker Framework does something like the above, but it has limitations due to being a 3rd party tool, it's trying to solve a more general problem, and personally I haven't seen it in widespread use (is it just me?)</div><div><br></div><div>Thoughts?</div><div><br></div><div>-Archie</div><div><br></div><div dir="ltr"><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature">Archie L. Cobbs<br></div></div>
</div>
</div>
</div>
</div>