Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I think the succinct summary of why null is undesirable is that <strong>meaningless states should not be representable</strong>.</p> <p>Suppose I'm modeling a door. It can be in one of three states: open, shut but unlocked, and shut and locked. Now I could model it along the lines of</p> <pre><code>class Door private bool isShut private bool isLocked </code></pre> <p>and it is clear how to map my three states into these two boolean variables. But this leaves a fourth, undesired state available: <code>isShut==false &amp;&amp; isLocked==true</code>. Because the types I have selected as my representation admit this state, I must expend mental effort to ensure that the class never gets into this state (perhaps by explicitly coding an invariant). In contrast, if I were using a language with algebraic data types or checked enumerations that lets me define</p> <pre><code>type DoorState = | Open | ShutAndUnlocked | ShutAndLocked </code></pre> <p>then I could define</p> <pre><code>class Door private DoorState state </code></pre> <p>and there are no more worries. The type system will ensure that there are only three possible states for an instance of <code>class Door</code> to be in. This is what type systems are good at - explicitly ruling out a whole class of errors at compile-time.</p> <p>The problem with <code>null</code> is that every reference type gets this extra state in its space that is typically undesired. A <code>string</code> variable could be any sequence of characters, or it could be this crazy extra <code>null</code> value that doesn't map into my problem domain. A <code>Triangle</code> object has three <code>Point</code>s, which themselves have <code>X</code> and <code>Y</code> values, but unfortunately the <code>Point</code>s or the <code>Triangle</code> itself might be this crazy null value that is meaningless to the graphing domain I'm working in. Etc.</p> <p>When you do intend to model a possibly-non-existent value, then you should opt into it explicitly. If the way I intend to model people is that every <code>Person</code> has a <code>FirstName</code> and a <code>LastName</code>, but only some people have <code>MiddleName</code>s, then I would like to say something like</p> <pre><code>class Person private string FirstName private Option&lt;string&gt; MiddleName private string LastName </code></pre> <p>where <code>string</code> here is assumed to be a non-nullable type. Then there are no tricky invariants to establish and no unexpected <code>NullReferenceException</code>s when trying to compute the length of someone's name. The type system ensures that any code dealing with the <code>MiddleName</code> accounts for the possibility of it being <code>None</code>, whereas any code dealing with the <code>FirstName</code> can safely assume there is a value there. </p> <p>So for example, using the type above, we could author this silly function:</p> <pre><code>let TotalNumCharsInPersonsName(p:Person) = let middleLen = match p.MiddleName with | None -&gt; 0 | Some(s) -&gt; s.Length p.FirstName.Length + middleLen + p.LastName.Length </code></pre> <p>with no worries. In contrast, in a language with nullable references for types like string, then assuming</p> <pre><code>class Person private string FirstName private string MiddleName private string LastName </code></pre> <p>you end up authoring stuff like </p> <pre><code>let TotalNumCharsInPersonsName(p:Person) = p.FirstName.Length + p.MiddleName.Length + p.LastName.Length </code></pre> <p>which blows up if the incoming Person object does not have the invariant of everything being non-null, or</p> <pre><code>let TotalNumCharsInPersonsName(p:Person) = (if p.FirstName=null then 0 else p.FirstName.Length) + (if p.MiddleName=null then 0 else p.MiddleName.Length) + (if p.LastName=null then 0 else p.LastName.Length) </code></pre> <p>or maybe</p> <pre><code>let TotalNumCharsInPersonsName(p:Person) = p.FirstName.Length + (if p.MiddleName=null then 0 else p.MiddleName.Length) + p.LastName.Length </code></pre> <p>assuming that <code>p</code> ensures first/last are there but middle can be null, or maybe you do checks that throw different types of exceptions, or who knows what. All these crazy implementation choices and things to think about crop up because there's this stupid representable-value that you don't want or need.</p> <p><strong>Null typically adds needless complexity.</strong> Complexity is the enemy of all software, and you should strive to reduce complexity whenever reasonable.</p> <p>(Note well that there is more complexity to even these simple examples. Even if a <code>FirstName</code> cannot be <code>null</code>, a <code>string</code> can represent <code>""</code> (the empty string), which is probably also not a person name that we intend to model. As such, even with non-nullable strings, it still might be the case that we are "representing meaningless values". Again, you could choose to battle this either via invariants and conditional code at runtime, or by using the type system (e.g. to have a <code>NonEmptyString</code> type). The latter is perhaps ill-advised ("good" types are often "closed" over a set of common operations, and e.g. <code>NonEmptyString</code> is not closed over <code>.SubString(0,0)</code>), but it demonstrates more points in the design space. At the end of the day, in any given type system, there is some complexity it will be very good at getting rid of, and other complexity that is just intrinsically harder to get rid of. The key for this topic is that in nearly <em>every</em> type system, the change from "nullable references by default" to "non-nullable references by default" is nearly always a simple change that makes the type system a great deal better at battling complexity and ruling out certain types of errors and meaningless states. So it is pretty crazy that so many languages keep repeating this error again and again.)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload