TypeScript: [Request for feedback] Nullable types, `null` and `undefined`

With the work on Nullable types in https://github.com/Microsoft/TypeScript/pull/7140, we would like to field some user input on the current design proposal.

First some background:

null and undefined

JavaScript has two ways that developers use today to denote uninitialized or no-value. the two behave differently. where as null is completely left to user choice, there is no way of opting out of undefined , so:

function foo(a?: number) {
   a // => number | undefined
}

function bar() {
    if(a) return 0;
}

bar() // => number | undefined

a will always implicitly has undefined, and so will the return type of bar.

Nullability

Given the JS semantics outlined above, what does a nullable type T? mean:

  1. T | null | undefined
  2. T | undefined

1. T | null | undefined

It is rather subtle what ? means in different contexts:

function bar(a: number?, b?: number) {
  a // => number | undefined | null
  b // => number | undefined
}

2. T | undefined

This is more consistent, the ? always means to | undefined; no confusion here.

T?? can be used to mean T | undefined | null or T? | null

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 5
  • Comments: 162 (81 by maintainers)

Commits related to this issue

Most upvoted comments

TL;DR: Flow was right, T? should be T | null. Seeking symmetry around x?: number and x: number? is a trap.

Let me put forth a new option: Unifying around the idea that undefined is the missing value and null is the non-value, with the logical outcome being that T? is actually T | null and T?? is T | undefined | null

Motivating example 1: Functions

function fn(x: number?) { /* implementation here */ }
fn(undefined); // OK
fn(null); // Error
fn(); // Error

This is very much wrong. Outside of dodgy stuff like checking arguments.length, the implementation of fn cannot distinguish fn(undefined) from fn(), but we’re saying the latter is an error and the former is OK. And at the same time, we’re saying that fn(null) is wrong even though fn tried to say that it could accept a number?, which raises the question of what we even call this kind of type since “nullable” is clearly off the table.

Let’s consider instead that undefined means “missing”.

function fn(x: number?) { /* implementation here */ }
fn(undefined); // Error, x is missing
fn(null); // OK
fn(); // Error, x is missing

Now the cases where fn can distinguish what got passed to it actually correspond to what the compiler would consider valid. A 1:1 correspondence between runtime and compile-time behavior should be a good sign.

Then consider the optional parameter case:

// Callers see this as (x?: number) => void.
// *Not* equivalent to (x: number?) => void
function fn(x = 4) {
  // No need for null/undef checks here
  // since x can't be null
  console.log(x.toFixed());
}
fn(undefined); // Allowed
fn(null); // Error, can't assign null to number
fn(); // Allowed

This behavior is the same as Option 1 above, except that we don’t need to think about how to introduce null back into the type system.

Combining the two:

function fn(x: number? = 4) {
  // Must check for 'null' in this body
  if(x === null) {
    console.log('nope');
  } else {
    console.log(x.toFixed());
  }
}
// All OK, of course
fn();
fn(null);
fn(3);
fn(undefined);

Motivating Example 2: Objects

interface Point {
  x: number?;
}
var a: Point = { x: undefined };

Should this be legal? If we asked a JS dev to code up an isPoint function, they would probably write:

function isPoint(a: Point) {
  // Check for 'x' property
  return a.x !== undefined;
}

Again, by saying T? is T | undefined, we’ve diverged the type system behavior from the runtime behavior as commonly seen in practice. Not good.

Separating concerns and T?? JavaScript code has two concerns to deal with:

  • Does this property exist (or for variables, is it initialized) ?
  • Does this property have a value ?

The first concern, existence or initialization, is checked by using === undefined. This tells if you if a property exists or a variable has been initialized. “Present according to hasOwnProperty, but has the value undefined” is not a state that the vast majority of JS code recognizes as being meaningful.

The second concern, having a value, is conditional on existence/initialization. Only things that exist and aren’t null have a value.

It’s a mistake to try to merge these concepts into one unified thing. See the section on undefined as sentinel for more comments on this.

For an arbitrary x, there are four states we can be in:

  1. x has a value (T)
  2. x might be missing, but if it’s not missing, it has a value (T | undefined)
  3. x is not missing, but might not have a value (T | null)
  4. x might be missing, and if not missing, might not have a value (T | undefined | null)

Given this:

  • Clearly T is state 1 (T)
  • Clearly T?? is state 4 (T | undefined | null)
  • Unclear: Is T? state 2 (T | undefined) or state 3 (`T | null)?

We can answer this by writing two declarations:

function alpha(x?: number) { }
function beta(x: number?) { }

If we believe in separation of concerns, as I think we should, then:

  • alpha corresponds to state 2, T | undefined. When x is not missing, it has a value.
  • beta corresponds to state 3, T | null. x is not missing, but may not have a value.

In other words, x: number? is number | null. x?: number is number | undefined.

undefined as sentinel and the pit of success Our informal twitter poll showed ~80% of people using null in some regard. I think this makes a lot of sense and we shouldn’t steer people away from that pattern based on our bias of how the compiler happens to be implemented.

One thing to notice in the compiler implementation is that we actually have lots of non-values that are implemented as sentinel references that could have been null instead, and vice versa. For example, unknownType and unknownSymbol are, in the vast majority of cases, simply checked for reference identity the same way we would for null if that keyword were allowed in our codebase. Indeed, failing to check for unknownSymbol is a common source of bugs that we could avoid if we had a null-checking type system and used the null symbol to mean unknown (thus ensuring that all necessary code guarded against it).

Conversely, we use undefined as a sentinel in dangerous ways. An informative thing to do is look at where in our codebase we write expr === undefined where expr is a bare identifier (not a property access expression, where we’re usually checking for an uninitialized (i.e. missing!) field). In nearly every case, we’re using undefined as a sentinel value, in which case the choice of null / undefined / reference sentinel is immaterial, and arguably poorly chosen.

For example, this code uses undefined as a sentinel (comment is as written):

    const arg = getEffectiveArgument(node, args, i);
    // If the effective argument is 'undefined', then it is an argument that is present but is synthetic.
    if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {

The implementation is:

function getEffectiveArgument(node: CallLikeExpression, args: Expression[], argIndex: number) {
    // For a decorator or the first argument of a tagged template expression we return undefined.
    if (node.kind === SyntaxKind.Decorator ||
        (argIndex === 0 && node.kind === SyntaxKind.TaggedTemplateExpression)) {
        return undefined;
    }
    return args[argIndex];
}

Where’s the bounds checking in that code? It’s in the calling code:

    const argCount = getEffectiveArgumentCount(node, args, signature);
    for (let i = 0; i < argCount; i++) {
        const arg = getEffectiveArgument(node, args, i);

How do we know that getEffectiveArgumentCount and getEffectiveArgument agree in implementation? They’re implemented over 200 lines apart. If we go out-of-bounds at return args[argIndex];, the manifestation is going to be very, very subtle. We could have allocated a Expression syntheticArgument sentinel and checked for that instead, turning out-of-bounds errors into immediate crashes. Alternatively, we could have used null to indicate that the argument is synthetic. Using undefined here seems simply dogmatic – two safer values were available, but not used. The fact that we generally get away with this sort of thing shouldn’t be seen as a positive case for using undefined as a universal sentinel.

Using undefined for nullability is emphatically wrong. Code that does so is either broken or is waiting to be broken.

I think that by not introducing x: T? for x: T | null, but preserving x?: T for x: T | undefined, TypeScript is actually encouraging the brokenness, because one has a shorthand and the other does not. Of course the one that has the shorthand is going to be used.

TL;DR: T? = T | null is the right way to go.

This is going to be a though sell because you have chosen to explicitly forbid null from your codebase, so you have bias. Please think about this issue with an open mind and ask yourself not what would suit your code today, but what is the most logical, coherent way to design future projects.

I would also stress out that this is not bikeshedding, it’s important. Yes the way the type system work is mostly worked out, and T? is “just” syntactical sugar. But as a built-in language facility, it’s going to become prescriptive of what an empty value is. New programmers coming to TS will learn that T? is possibly missing T and they will use just that. None of them are going to say: “oh that’s so a bad choice, I’m going to use type Opt<T> = T | null | undefined”. It is nice that you can do that, but the reality is that most programmers won’t. And even if Opt<T> is ok, it’s nowhere as convenient as ? for stuff that is going to be used a lot and sometimes composed (try Opt<Opt<number>[]>).

I was happy to see @RyanCavanaugh’s long comment above because I agree 100% with him. 👍

He explains clearly that in JS undefined represents non-existent values: properties that are not defined, variables that are not initialized, missing keys in a Map.get(), missing items in an Array.find(), optional parameters, out of bounds array accesses and more. This is not the same thing as null which is a defined value, that denotes the “empty”, “none” concepts.

I believe that the shortcut T? should denote what we want people to commonly use to indicate an empty value. And at the core of javascript, a known empty value is null.

Using undefined as your empty value is wrong: you can’t make the difference between a missing optional parameter and a passed empty value.

// This:
function f(n?: number) {
 // n is either a non-null number, or an optional parameter (undefined)
}
// Is very different from this
function f(n: number?) {
 // n is a mandatory parameter, which might be null
}
// Which is very different from this
function f(n?: number?) {
  // n might be an optional parameter, or it was passed with a possible null value.
}

You can’t make the difference between a key that is not present in a Map or a stored empty value:

let map: Map<string, number?>;
let x = map.get("key");
// x is undefined to indicate "key" is missing in the map.
// otherwise x is defined but might be null

JS is a dynamic language, if you assume dog: Dog? property on objects, having null means: “I don’t have a dog”. If your objects does not have a dog property, you get the undefined value, which means “I don’t know if this person actually has a dog or not, the information is missing”.

As @RyanCavanaugh pointed out, “empty values” are an often required concept. So if you forbid null you end up needing sentinel values… which actually are exactly as null but without the awesome new TS null safety checks.

For performance, you should define your object members and use null. Modern JS engine optimize for monomorphism. When you declare a class, you should set all its properties in its constructor and never add or remove one afterwards. Leaving everything undefined means that each time you set one property your class shape changes. Even worse: if you don’t set them in the same order in every code path, you end up with different shapes! This hurts the JIT which generates less performant code.

Preserving the difference between undefined and null is also interesting when debugging. If my program crashes on x.hello() and x is undefined, I know that I have a different kind of problem vs if x is null. In the former it’s probably because I used a codepath where x was not initialized at all. In the latter it’s that my code was not expecting an empty value. And the interesting point here is that with TS 2.0 you should never get null errors ever. On the other hand undefined are unfortunately always possible because of JS nature (e.g. an out-of-bound array access).

If you write your programs this way, I don’t even believe that T?? is something that we need. You would declare nullable types as T? meaning T | null. Optional parameters are implicitely undefined: f(x?: T) is in fact T | undefined. Optional members are implicitely undefined as well: { x?: T } is in fact T | undefined. In itself, undefined (as a type) would almost never been used verbatim because it pops up mostly from the JS runtime. The only place where I would still see undefined used is as the return type for functions that want to say “no result”, for example: Array<T>.find((T, number) => boolean): T | undefined And to be honest in those very rare cases I would prefer typing T | undefined rather than the cryptic T??. Incidentally that also implies T?? = T | undefined which is probably not what you’d expect.

I could also live with proposal 7… it’s the non prescriptive, everybody-is-happy middle ground. The drawback is that this is cryptic like hell.

@jesseschalken We can always add T? later, but we can’t take away x?: T. I should add that we’re also planning to introduce two type aliases in lib.d.ts:

Nullable<T> = T | null;
Optional<T> = T | undefined;

@xogeny There is a slight difference and it applies equally to optional parameters and optional members. The ? notation is not just a shortcut for T | undefined. It actually means that the parameter/member is optional and this is a distinct concept.

If you declare

function x(n?: number) {}

Then the parameters n is optional and you can call the function with x(). Of course in that case, n would be undefined in the function body, which is why its type definition is extended to number | undefined. But this rather a side-effect of optionality. Writing function x(n?: number | undefined) is legal and would be strictly equivalent.

On the other hand, if you declare

function x(n: number | undefined) {}

Then the parameter is not optional and you have to pass it, even when undefined, like so: x(undefined). Calling x() is not legal in that case.

The optionality part can be seen more clearly if you try to add more parameters. The following declaration is illegal: function x(a?: number, b: number) because all parameters after the first optional parameter have to be optional as well.

It works the same for optional members. I agree with you that { x?: number } and { x: number | undefined } are mostly equivalent, but strictly speaking, there are differences. Consider the following examples:

'x' in { x: undefined } === true;
'x' in { } === false;

Object.assign({x: 3}, {x: undefined}) // == {x: undefined }
Object.assign({x: 3}, { }) // == {x: 3}

I started a poll on T? https://twitter.com/basarat/status/709515537580105728 (still in progress but I don’t need to wait for it to end to see the pattern). The results are very similar to the non syntax poll by the TypeScript team https://twitter.com/typescriptlang/status/707007550659493888

My analysis:

  • People use both null and undefined. Some people try to use explicit null only, others try to use undefined only.

The only way a T? syntax can accommodate all these people is if T? means T | null | undefined. The null / undefined are generally typeguarded with !=null or !=undefined anyways so the following should just work:

function foo(something:string?) {
    if (something == null) {
        return something
    }

    // `something` is now `string` 
}

For people that do care about the strong consistency of null or undefined the interfaces Nullable / Optional can be used.

I don’t think there will ever be a consensus for T? to mean only null or only undefined.

This is all purely my opinion 🌹

My vote: Allowing null to be a valid nullable aka T | null | undefined

Reasons:

@RyanCavanaugh looking back I did originally misread the question here 🌹 😃

both null and undefined can only encode a single bit of information , there is nothing that make one bit of information more expressive than another bit of information hence no distinction, am I clear so far?

now have 2 indistinguishable options and we need to make a hard choice, JavaScript to the rescue, undefined is already used by JavaScript for representing missing values, JavaScript doesn’t use null for anything saving it for humans, humans are free to use it anyway the want

here comes the point, undefined is better for missing values because the choice was already made in its favor, let’s just stick with it and let null go if we have to make this choice and define what T? is

I personally think that T? idea should be given more time, the optional feature should be shipped backed by unions and aliases

@aleksey-bykov Please be constructive and take a few moments to think about what kind of arguments other people might find convincing, rather than just conveying your own emotions about the issue. Just coming in here and saying something is “embarrassing” or “nonsense” or “a shame” isn’t going to change anyone’s mind, and isn’t useful evidence. The proposal of “Don’t add T?” has been recognized and is something we will be giving due consideration to.

As a practical matter, if we shipped this feature without T? meaning anything, we’re going to get six issues a day from people suggesting both “Have T? be a shortcut for T | undefined” and “Have T? be a shortcut for T | null”. I guarantee it. There’s also strong precedent here as T[] is already sugar for Array<T> (which no one seems to complain about).

?T? this is just embarrassing… seriously? people are going to be pointing at me and saying: look here comes the ?T? guy, what a shame

seriously let’s stop this question-sign-here-and-there nonsense!

why?

has any one asked themselves a question: why we need this new syntax when there are unions here available already right now

I vote for 4 or 5, T? = T | null, because I agree that nullability and optionality are separate, orthogonal concepts, corresponding to values null and undefined respectively, and TypeScript already has a shorthand for optionality, x?: T. The only thing that is missing is a shorthand for nullability, x: T?. If you want both, you can get both with x?: T? (or x: T?? for option 5).

I see no reason to conflate the two concepts, and I especially wouldn’t want parameters and properties to be unintentionally optional (by including undefiend) because they were made nullable with T? instead of T | null.

From a developer ergonomics perspective, 99% of the time I care about nullability it’s because I want to know “is it safe to call foo.bar() on foo?”

So for me it comes down to: will the nullable type support help the compiler prevent me from doing the following mistake?

let x = document.getElementById('nonexistent-id');
x.innerText = 'hello';

Unfortunately, it appears that x is null in the above, so I’m not sure how we can type getElementById unless it has return type HTMLElement? and that type must include null. From that perspective null and undefined should be treated the same.

Finally, based on my “99% of use cases” metric, the proposal variant involving ?? makes the language a lot uglier for not a proportional amount of gain.

Regarding optionality and confusingness (the symmetry arguments), I don’t mind asymmetries because the two question marks in foo(x?: number?) mean totally different things to me. The first one is a modifier on the function itself – note that it modifies the arity of the function – and the second is a statement about the type of x (e.g. it could just as well be replaced with a typedef). Perhaps it’s just because I’ve looked at too much closure code, which uses = instead of ? for optional arguments though!

JavaScript object is a hashtable that returns null instead of throwing an error

*typo (I think). You meant to say undefined. I agree with you. Crockford agrees with you. Its undefined all the way. But I just have null inconsistently because of dealing with dom / regex / nodejs 😃 So I end up with both 😕. But never check explicitly against one … just == null or == undefined (either works for both) 🌹

You asked me where undefined vs null would actually make any difference

Not really. My question was actually asking: Is it wise to take vastly differing actions based on whether the outcome is null vs. undefined, considering there is no consistency in their provenance.

TS should deviate from the JS specs by promoting your view

No, I disagree with your interpretation of the JS spec. The arguments that you have mounted in favour of using null now appear to have degenerated to “because it’s in the spec”. I’m saying the spec is neutral on this.

@NoelAbrahams Do I understand you correctly? Basically, you’re saying that you’re not convinced by using null the way ECMAScript says you should (even in the most recent specs); and that TS should deviate from the JS specs by promoting your view?

you would like to use null so that one can write code like your memoisation helper.

You asked me where undefined vs null would actually make any difference, as if the points I made previously where purely rhetorical. I showed you a common abstraction as an example.

If you use null as your intentional empty value then the memoization code above works perfectly. If you choose undefined you’ll have to add a few workarounds to have a generic, working memoization. It’s not impossible mind you. It’s just friction because the JS libraries (here Map) use undefined with a defined meaning that you do not want to adhere to.

That is precisely what I am arguing against. I do not advocate writing code based on the nuances of null and undefined.

I respect your choice and I don’t want you to change your programming style. But I think you need a stronger argument for TS to encourage your style vs the official documentation of JS that can be read over the internet. Here’s the first StackOverflow result on the subject. I don’t think there’s a precedent for TS not following JS specs.

BTW you won’t avoid null completely as some API returns it, for instance document.getElementById() returns null if there is no matching element in the DOM.

And honestly: there are no “nuances”. It’s easy if you take 2 minutes to read the one-liner description of each (and you end up using null 99% of the time). If you think I’m wrong, please post an example where the concept is confusing. There are a few API where the return type might not be obvious – e.g. should getElementById have returned undefined? No library is perfect, but thankfully with static typing this isn’t an issue.

My view is that it is a historical mistake

Do you have any insight here? I am in no position to comment about that. Anyway we have to live with our mistakes. TS needs to embrace JS as it is defined today. BTW the fact that it’s a mistake doesn’t seem to make consensus judging by the polls and discussions.

The fact that map.get returns undefined does not prove anything in your favour. It does so for the same reason that indexing into an object literal or an array returns undefined.

Yes, and this reason is “by definition of undefined: the key has not been assigned a value”. It proves that modern APIs still follow the old semantics. If ES committee wanted to design modern API so that null was not used anymore, they would have modelled it like .NET did with Dictionary: throw an exception if the key is not found or return a tuple (the out param in TryGet). They certainly would not have created an API that does not distinguish between “no key” and “empty value”. That is just assuming bad API design.

@jods4,

Easily. Let’s try to write a memoization helper:

function memoize(f) { let map = new Map(); return function (x) { let cached = map.get(x); if (cached === undefined) { cached = f(x); map.set(x, cached); } return cached } }

If I understand you correctly, you would like to use null so that one can write code like your memoisation helper.

That is precisely what I am arguing against. I do not advocate writing code based on the nuances of null and undefined.

As to your assertion that somehow null is a carefully planned ECMASCript standard, I do not hold that view. My view is that it is a historical mistake. If we could start again there would be only one concept for missing/empty values. Writing code should be about reducing complexity - not enhancing it by introducing highly nuanced concepts.

The fact that map.get returns undefined does not prove anything in your favour. It does so for the same reason that indexing into an object literal or an array returns undefined.

Oh wow, I missed the whole “assigned-before-use checking” thing. +1, disregard everything I said in the last couple days. 😄

There are lots of things in the official JS standard that are there for historical reason. No one is going to argue that automatic semicolon insertion is a good thing, but that’s part of the standard too.

Regarding Optional, just a note that Swift uses Optional<T> for the algebraic option type (i.e. Nothing | Just t), and many other languages (Rust, Scala etc) use Option<T> to mean that too. Might be a bit misleading to call it that (Though I suppose in the context of JS it makes sense)

Current Status

Thanks everyone for the comments so far; it’s been very instructive. Dan posted some notes in #7488. Here’s where are after the design meeting yesterday.

In the absence of a strong consensus (possible understatement of the year), we felt it was better to not have T? as a type syntax sugar for now. Everything else about this feature is relatively clear and it would be problematic to have a new syntax with unclear meaning be confusing users when it first appears.

Additionally, there was a relatively good consensus that making definition file authors actually think (e.g. read the docs) about whether a particular function might return null or undefined is a very good thing, rather than lots of people assuming that T? is the right shortcut for whatever the function/property might do.

Existing described behavior around optional properties and optional parameters remain unchanged, changing the implicit type of x?: string in those positions to string|undefined. Additionally, the “possibly-uninitialized” variable syntax (let x?: string) will be supported, also implying T | undefined.

Going forward we’ll be looking at what nullability-compliant definition files on DefinitelyTyped look like. If there’s significant preponderance of T | null | undefined, T | undefined, or T | null appearing in well-formed definition files, we can certainly revisit the topic and see if it makes sense to introduce a T? shortcut.

@tinganho I’m not concerned about abuse so much as precision. I want a type checker, type system and type language that is precise (and encourages precision).

With T? = T | null, every combination for null and undefined can be written in shorthand:

Shorthand Longhand -
x: T x: T (existing)
x?: T `x: T undefined`
x: T? `x: T null`
x?: T? `x: T null

For T | undefined, the ? against the identifier rather than the type is correct because “may be undefined” means the same thing as “may not exist” and “may not be set”. It reflects JavaScript’s predefined meaning for undefined as the value of things that don’t exist or weren’t set.

The only thing lacking is a shorthand for T | undefined if you need it as a standalone type. But if undefined and null are being used correctly, you won’t actually need it.

@aleksey-bykov You said yourself “undefined it’s a bit different because the JavaScript runtime chose to use it for certain situations” and now you’re saying “distinction between null and undefined is an illusion nourished by ignorance”. Are you actually going to bother making an argument or just continue spewing angry gibberish?

arument about dual meaning to undefined is sucked out of the thin air, whenever you see null you have no clue why it is there, null can’t encode anything but a single bit of information, when it comes to a reason why null is there in the first place it just leaves you guessing

if the reason as to why a value is missing is important than null is a poor choice because as was said it doesn’t have any expressive power but to state its presence

one should consider an enum or more elaborate structure to encode different possible reasons if they matter

distinction between null and undefined is an illusion nourished by ignorance

null has no standard meaning, who ever says it does is living in the dark On Mar 11, 2016 10:10 PM, “Jesse Schalken” notifications@github.com wrote:

@tinganho https://github.com/tinganho It’s not necessarily needed, since a function should either return nothing (undefined), or return something nullable (T?). If you have a function that returns T? but it returned undefined, that means you are missing a return … somewhere in the function body. (Again, it’s important to distinguish between null and undefined here, because the occurrence of undefined indicated a programming error.)

However, I would prefer ?: be allowed for a return type regardless, purely for consistency, and it would read as “may return …”. It is surprising when type annotations that work in some places don’t work in others. If ?: T is just shorthand for : T | undefined, you might as well allow it everywhere.

— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/7426#issuecomment-195647337 .

to all you guys who keeps arguing about meaning of null and undefined, I hope you realize that those are just 2 values no different from 427 or ‘hello world’

you can as well use the string ‘this value is missing’ for what you typically need null

although for undefined it’s a bit different because the JavaScript runtime chose to use it for certain situations

there is no intrinsic meaning to null other that what you want it to be, because it is just a value, just like you can use 634 to secretly call you boss a dumbass in your emails

the only thing that makes null type/value special is that there are almost no operations defined on it (other that equality, inequality and coercion to string), these scarce capabilities are a perfect excuse for choosing it to encode a missing value, a missing value is encoded by the least capable type

but again it only a matter of habbit, there is no thing on earth that stops you from using it to encode something completely different, like you dog’s name: whenever you see null in my app you should read it “smokey”

let’s please avoid making it a done deal that T? is T | null, because if you think null is a special value that can only mean the absence of thereof, you are wrong, null only means whatever you want it to be

in this regard undefined is a much better option, because it’s not you, but JavaScript runtime that makes it different by giving it a special meaning of a missing value On Mar 11, 2016 8:43 PM, “Jesse Schalken” notifications@github.com wrote:

If my program crashes on x.hello() and x is undefined, I know that I have a different kind of problem vs if x is null.

Exactly. Because undefined only exists for the purpose of being the value of things that don’t exist or weren’t set, you know that if you hit undefined, that’s what has happened.

If a developer uses undefined instead of null for things that are nullable, they won’t be able to tell whether something is undefined because it was explicitly set as such or because it wasn’t set at all, and there will always be this strange thing called null which doesn’t fit into their mental model, which they have no use for, but they nonetheless encounter sometimes.

— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/7426#issuecomment-195628945 .

@malibuzios We have indeed considered permitting the ? name modifier in other places. In particular:

  • On local variable names (e.g. let x?: number).
  • On property declarations in classes (e.g. class Foo { x?: number }).
  • On call signatures (e.g. function val(x?: string)?: number).
  • On index signatures (e.g. [x: string]?: Entity).

The effect would be to include undefined in the type, just like optional parameters and properties.

I think allowing ? on variable and class property names has a lot of merit. Permitting ? on signatures would be nicely symmetric, but its meaning would perhaps be less obvious.

@aleksey-bykov If I may chime in… and this may be a bad idea…

You’ve definitely made your point about not liking T?, but you should probably acknowledge the relative weakness of your argument… if you don’t like the feature, you don’t have to use it in your own code (as you’ve pointed out, Nullable<T> or T | null will work just fine) and can even submit a PR for TSLint for a rule to prevent its usage.

This means you really don’t benefit much from not having this feature included, unless the continued bikeshedding leads to a delay of the entire nullability system itself (which, as far as I can tell, is not what is happening now, that PR looks a pretty long way from done). On the other hand, those who would prefer a more concise syntax would be left without one if you “win” this argument - a lose-win which could have been a win-meh.

Everyone’s perspective here is important in debating such a complex language feature… but I’d like to see the discussion be more positive so I at least can feel like a happier person while reading it. There’s no reason to be angry about this.

So we have 4 different perspectives

  1. I never use undefined, always null
  2. I never use null, always undefined
  3. I use both as appropriate, and would like to differentiate between the two
  4. I don’t want to care

Perspective 1

Great, but there are some cases where undefined is unavoidable.

  1. testing for an object property, we must compare with undefined or at least weak-compare with null i.e. x != null
  2. testing for an optional argument, we must adhere to ES6 specs which will always strictly compare with undefined i.e. !== undefined

Conclusions:

  1. If T? means T | undefined and there is no shorthand for T | null, that would be annoying. Additionally many DOM APIs would have verbose looking type definitions
  2. If T? means T | null, and there is no shorthand for T | undefined, this is generally okay. There are still however cases where you will need to use T | undefined like e.g. property access of any arbitrary dictionary.
  3. If T? means T | null | undefined, we would have a problem when using strict null comparisons like x !== null. That would fail to eliminate undefined. Therefore we would have to either use T | null or x != null, which can be substantially slower for high performance code.
  4. The type of current optional arguments must be precisely T | undefined

Perspective 2

This is a workable strategy but its uncommon. Additionally, the DOM APIs often return null which makes null important enough to model.

Conclusions:

  1. If T? means T | undefined, and there is no shorthand for T | null, that would be mostly okay. Except for the DOM apis, which return nulls and will have verbose types to deal with. Additionally we would would have 3 ways to do the same thing: {x?: string} or {x: string?} or {x?:string?}
  2. If T? means T | null and there is no shorthand for T | undefined, this will be somewhat annoying. While optional arguments and fields are still possible to express, return values that can be undefined and casts to “undefinedables” will be unnecessarily verbose.
  3. If T? means T | null | undefined, we would have a problem if we are using strict comparisons with undefined like x !== undefined, which fails to eliminate null. We would have to choose between non-strict comparisons x != undefined (can be similarly slower in high performance code) or use T | undefined explicitly

Perspective 3

I use both as appropriate, and would like to differentiate between the two

  1. T? means T | undefined not okay
  2. T? means T | null okay, but T | undefined has no sugar. Not necessary in most places.
  3. T? means T | null | undefined - same problem with strict comparisons

Perspective 4

Here non-strict comparison i.e. x != null is used and usually there is no differentiation between null and undefined / missing values. Which means:

  1. T? means T | undefined not okay, null is missing (common node example: cb(null, res))
  2. T? means T | null - slightly better ({x?:number?} can do both), but still troublesome.
  3. T? means T | null | undefined - fine. Additionally there is no problem with default arguments because their type is still different and correctly modelled.

Note that the compiler source code seems to treat nullability and optionality as the same concept (by using undefined only and no null).

So? My codebase uses null and never undefined (except to check optional properties/parameters). I posit that this is correct, because undefined has reserved meaning in JavaScript as the value of properties that don’t exist and parameters that weren’t passed, whereas null has no reserved meaning and is therefore appropriate to use for the purpose of a nullable type.

JSON matches this, too, which has null as a legitimate value but not undefined, because a property with the value undefined is considered the same as one which doesn’t exist (JSON.stringify({foo: undefined, bar: null}) === {"bar":null}).

Also a some functions in that file take undefined as a valid argument without actually saying that the argument is optional.

When a parameter is not passed, it is undefined. According to ES6, the correct way to check if an optional parameter was passed is with !== undefined or typeof x !== 'undefined'. Therefore, “is optional” and “accepts undefined” are precisely the same thing, null is not a valid value for an optional parameter, and so null and undefined must be tracked separately.

This was already specified as outside the discussion by @ahejlsberg:

A few assertions to ground this discussion:

  • The semantics of T | null, T | undefined, and T | null | undefined are not in question and we’re not proposing any changes from what is described in the introduction to #7140.
  • An optional parameter or property declaration automatically adds undefined (but not null) to the type of the parameter or property (i.e. x?: T is the same as x?: T | undefined) and we’re not proposing changes to this.

I think there is no smoothing over the difference between null and undefined, or between nullability and optionality, and the question really is whether the shorthand for nullability, T?, should imply optionality. I say that it shouldn’t, because we already have a shorthand for optionality, x?: T, which you can use if you want optionality.

I prefer solution 1.

Though, I wonder if anyone have considered separating the meaning of ? for property names and type names?

  1. ? for property/variable/param names means T | undefined for whatever annotated type.
  2. ? for type names means T | null.

Lets begin with explaining the latter 2. first. I think every JS function that are meant to return type T should return a value that corresponds to type T or null. This is consistent with other browser API:s such as document.getElementById('none'); // null. This is also consistent with other programming languages.

For 1.. There is already a way of defining optional params and properties in Typescript. And it means T | undefined right now.

undefined means uninitialized and null means novalue. Checkout this SO thread http://stackoverflow.com/questions/5076944/what-is-the-difference-between-null-and-undefined-in-javascript. Many programmers that come from other programming languages have hard time to separate the meaning of null and undefined. Though I believe that pure JS developers don’t have this problem.

C++ also default initializes to undefined behaviour and not null. Javascript is just a copy of that.

Also quoting one of the comments of the first answer in the SO thread:

You may wonder why the typeof operator returns ‘object’ for a value that is null. This was actually an error in the original JavaScript implementation that was then copied in ECMAScript. Today, it is rationalized that null is considered a placeholder for an object, even though, technically, it is a primitive value.

One of the downside, is that we must deal with how to handle uninitialized variables today:

let s: string;// string | undefined

The proposed syntax is:

let s?: string;// string | undefined

So there is quite a LOT of code that must be rewritten. BUT probably as many lines as any non-null proposal.

Also one can define T | null | undefined as:

let s?: string?;// string | null | undefined

One other downside is, there might be a few people who have written function that returns T | undefined. In my view this is already broken in the first place. Though I think it is extremely rare. One could provide an escape hatch to define the correct type in those cases:

function getString(): string | undefined {
    if (Date.now() % 2 > 0) {
        return 'hello world';
    }
    return undefined;
}

Pros

  • consistent with the current JS and TS semantics.

Cons

  • Breaks existing code.

I prefer number? as a shorthand for number | undefined rather than number | undefined | null because it composes better (you can add null, it’s impossible to subtract it), and serves as a better indicator of how most builtins function. (I’d rather be sprinkling number?'s everywhere rather than number | undefined, that’s for sure!). So many builtin structures in JS (object indexes, for one!) only ever introduce undefined and never null - having to write that out by hand each time seems comparably tedious.

In any case, having base types which include neither value lets me create more accurate typings, this is just a contest for shorthand syntax. T? and T?? look both short and easy to work with, so I’m all for them, but I’ll easily write out type Nullable<T> = T | null; and type Undefinable<T> = T | undefined; in my own code, if need be. And I will treat them separately because I don’t want to assign something nullable to something undefinable, since then it could get a null, which would be bad.

My question was actually asking: Is it wise to take vastly differing actions based on whether the outcome is null vs. undefined , considering there is no consistency in their source.

OK. Let me answer that, then. Is it wise to take vastly differing actions based on whether a value is truthy (e.g. a number) vs true?

  • If we control the input (private API) then yes we assume we have consistency in our code and anything else is a bug. TS statically validates correct usage of our API and I look forward to strict null checks. I personnally hate overly-defensive code.
  • If I write a public API I would be more careful and accept a wider range of options, when possible.

No, I disagree with your interpretation of the JS spec. The arguments that you have mounted in favour of using null now appear to have degenerated to “because it’s in the spec”. I’m saying the spec is neutral on this.

Then we’ll have to agree to disagree. To me the definition of null and undefined in the spec leaves no doubt to their prescribed usage. Would you care to give me your interpretation of the specs? Can you explain what the difference between undefined and null is supposed to be?

The argument have not “degenerated” into a call to the specs. I think following the specs is the strongest argument to be made in the first place. It has far reaching implications and I think you need a very strong counter-argument not to follow it. Of course, that’s assuming we agree on the meaning of the specs, which we apparently don’t.

The rest is arguing about what programming style is better and let’s face it: there will be no consensus on that. If you want to tackle those arguments just go back to the vexing apis in this comment and make a point how much better using undefined is.

It would be much better if the language pushes them towards the standard, i.e. for a “normal” method: let x: number? = null; if (someThing) { x = 10; } return x;

@jods4

I think the “standard” depends on which person you ask. For more “managed languages” it usually default initialises to null.

But consider C++, where there are no default initialisation:

int someint; //undefined behaviour

I think JavaScript just adopted the semantics of C++ with the undefined keyword. That’s why I propose the let x?: number syntax.

C++ separates the concepts of undefined behaviour and nullptr. And I don’t see anything wrong with JS doing it too.

Correct me if I’m wrong, C++17 have proposed a not_null ptr. And it only addresses nulls and NOT undefines? So they where kind of in the same seat that TS are in. If they didn’t merge the concept of these two, nulls and undefines. Then why should TS merge the concept of these two?

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Ri-nullptr

The intuition here is that

let x?: number;
if (someThing) {
  x = 10;
}
return x;

declares a possibly-initialized variable that is legal to access before its first definite assignment, as shown here (the return type of the containing function now being x | undefined). This is “just JavaScript” in terms of undefined being the well-known value of non-initialized properties and variables.

The semantics here are basically identical to a possibly-initialized property of an object

interface Foo {
  x?: number;
}
let a: Foo = {};
return a.x;

@NoelAbrahams That’s very weak. null and undefined are hardly “historical” as they are still used in pretty much all JS written today. New “modern” APIs still follow the behaviour above to the letter. Map.get() is a recent example; it follows the original null vs undefined design. If the meaning of null was obsoleted we would have had a different API.

ASI is a terrible thing yet TS still follows the standard to the letter… so that’s not a precedent for diverging from JS, even though it’s horrible. Rather an argument that TS complies with JS specifications.

and break correct code that says

Is there any hazard in changing the code to:

if (x.errorCode != undefined) { 
  console.log(x.errorCode.toFixed()); // Okay now
}

When the definition author did :

interface Result {
  // Reality: is undefined or number
  errorCode: number?;
}

They really should have done:

interface Result {  
  errorCode?: number;
}

I think this is what lazy authors would do … but maybe not 🌹

My primary fear is that lazy .d.ts authors will be like

interface Result {
  // Reality: is undefined or number
  errorCode: number?;
}

and break correct code that says

if (x.errorCode !== undefined) {
  // Error?! x might be 'null'
  console.log(x.errorCode.toFixed());
}

getCompany example doesn’t prove that T? needs to be T | null

it only testifies that 2 different reasons for a missing value have to be considered

first reason (optional parameter) is naturally taken care of by JavaScript

second reason is business related, and up to the developer as to how it should be represented/encoded

you might think that null is a natural choice for it, very well might be so, but if this result is chained with something that expects null for a different reason then you are in trouble

my take that getCompany result might be declared as Company | null no problem but this special case where a missing value as a distinct flavor can’t be used as a universal recipe

a better solution is to add special types whose only purpose is to serve certain reasons for a missing value that can’t be intermixed

getCompany () : Company | CompanyNotAsigned | CompanyNotApplicable

or getCompany () : Company | NoCompany

const enum NoCompany { NotAssined, NotApplicable}

this way you:

  • can be more exact about the reasons
  • can avoid possible conflicts about meaning of null in different situations On Mar 13, 2016 8:32 AM, “jods” notifications@github.com wrote:

@NoelAbrahams https://github.com/NoelAbrahams

I do not think one needs null in anyway in order to create sealed classes. Properties on classes can be initialised with undefined and that would still work.

That is true. But I never see people initializing all their fields with x = undefined, they usually leave them uninitialized. People using null often initialize and with upcoming 2.0, they will have to.

— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/7426#issuecomment-195948410 .

@NoelAbrahams Read https://github.com/Microsoft/TypeScript/issues/7426#issuecomment-195743448. Boy does it grind my gears when people dismiss solutions to concrete problems as too “academic”. Using undefined for nullability can and eventually will break your code. If you want broken code, fine, but don’t expect the language and the other people who use it to bend to accommodate it.

@jesseschalken You’re dismissing all drawbacks…

About 'a' in x “it’s not checked by TS”. No but if I declare let x: { a: T | undefined } I can expect 'a' in x to always be true. Your suggestion breaks that.

About arguments.length “it’s ES5, ES6 does it differently” There is no ES6-compliant browser available today. Lots of code and people still use ES5 style. There’s no reason to break their code yet. If I declare f(x: T|undefined) I can expect arguments.length == 1. Not with your suggestion.

“people who use undefined for empty are wrong” Probably, but lots of people do so, their programs work and TS should support them.

The drawback you mention is additional complexity in the compiler but it’s the opposite. Signature of functions already have an optional flag on parameters (could not do otherwise before 2.0 anyway) and undefined typing works at the type checker level with union types. What you want needs to modify TS to mix the function signature with the actual type of its parameters. That’s additional complexity.

The drawback I see is loss of clarity. I hate that when I show you this piece of code: f(x: MyType) you can’t tell me if the parameter is optional or not, unless I give you the precise definition of MyType. That feels wrong.

I don’t get why you don’t want to mandate that optional parameters are declared as such? It’s a matter of type definition in your code or in the .d.ts declarations. If you want a parameter to be optional, say so. f(x?: T) clearly communicates intent, keeps function signature and typing separate and is shorter than the alternative notation.

Also right now the nullability debate is only localized to this github thread. If you add:

Nullable = T | null;
Optional = T | undefined;

The debate will span to all TS user.

Nullable<T> = T | null; Optional<T> = T | undefined;

After thinking about it. I think adding these types might be a mistake?. I guess you want add these to test the waters? Wouldn’t that eliminating the opportunity that T? meaning T | null. Because after the market have chosen. And let say that Nullable<T> = T | null becomes a clear winner with a clear majority. There will be still those who have written Optional<T> = T | undefined. Thus you cannot add T? = T | null, because that would mark Optional<T> = T | undefined as a faulty solution. Then the people who have written tells you it was you who added it. How can it be wrong? Let say that you tell them that they don’t need to rewrite. Then for those who use T? will be very frustrated that there exists Optional<T> a little bit everywhere.

I think it is best if the TS team bless one solution. Because non-null typing is already a breaking change. And if executed correctly we only need 1 rewrite. Though if you choose to test the waters people would need 2 rewrites.

For me, although a little bit biased, T? = T | null was a clear winner in this discussion.

And for your Twitter poll. Although I think you framed the question a little bit wrong. Because honestly I don’t think I understood the question totally or the question was a bit too ambiguous. People still chose a strong preference for null.

screen shot 2016-03-13 at 12 14 56

IMO TS should bless whatever JS have blessed. And looking at the evidence I think you cannot deny that you find T? = T | null to be truth.

So there is strong proof and there is empirical result suggesting T? = T | null. I don’t think you can ask for more?

@jods4 Oh, if you check arguments.length, yeah, but that’s cheating. ES6 specifies === undefined as the correct way to check an optional parameter, not arguments.length.

TS should help them if there’s no compelling reasons not to

The reason not to is that “includes undefined” and “is optional” then have to be tracked as distinct concepts, complicating the type checker and language semantics, to check something that actually makes no difference at runtime in an effort to help people do something that they shouldn’t be doing in the first place.

Final food for thoughts: in this case f(x: any), I assume you consider the parameter to be optional since any includes undefined? In TS 1.8 calling f() in this case is an error…

Calling f(x: any) as f() is debatable. The whole point of any is to opt out of checking, so you can’t expect TS to flag it. If you’re using any as the type of something you’re really in no position to claim TS should be throwing errors regarding it.

Calling f(x: {}) as f() on the other hand should be an error, and it is if I’m reading #7140 correctly.

@jesseschalken

are precisely the same thing

I’m probably nitpicking but no, not exactly. Some illustrations:

function f() { return arguments.length; }
f(); // returns 0
f(undefined); // returns 1

let x = { a: undefined };
'a' in x; // true
'b' in x; // false

And even if passing undefined is mostly the same at JS level, I find it more clean that the TS typing clearly reflects if a parameter is or is not optional, even if allowed values include passing undefined. Even if you think some people are “wrong” using undefined instead of null, TS should help them if there’s no compelling reasons not to.

Final food for thoughts: in this case f(x: any), I assume you consider the parameter to be optional since any includes undefined? In TS 1.8 calling f() is an error…

EDIT: couldn’t resist adding one more… Generics. Assume f<T>(x: T), is it consistent that f<number>() is an error, but f<Optional<string>>() is not? You could say yes but I find it a bit unsettling, especially in the presence of type aliases.

It would be unintuitive that you can call f(x: Something) without parameters because Something is a type alias that includes undefined.

A parameter that is not passed in JavaScript is given the value undefined. A property that doesn’t exist produces undefined when read. Therefore “is optional” and “may be undefined” are precisely the same thing. If the type checker treated f() and f(undefined) differently, it would imply that they are different at runtime, but they are not. That would be unintuitive.

If null and undefined are being used correctly, I can’t imagine a type alias including undefined being used. In fact, I can’t imagine undefined occurring in a type expression at all, since it has shorthand x?: T for the places where it’s appropriate.

So it’s not necessarily that f(x: Something) could be called f() that is unintuitive. It’s the fact that Something includes undefined that’s unintuitive. The developer is only going to get confused if they are already down the wrong path of treating undefined as a legitimate value for their own use rather than as reserved for use by the JS runtime.

Meaning of null and undefined

OK, let’s try to put a stop to the “meaning” of undefined and null madness. From the ECMAScript Language Specifications

undefined value primitive value used when a variable has not been assigned a value null value primitive value that represents the intentional absence of any object val

Of course you can do what you want in your code but this is the intended usage of undefined and null as prescribed by JS itself. I feel like TS should support any style of code, but encourage idiomatic JS.

Response to criticism of T? = T | null

From the TS design meeting notes #7488

People will definitely be confused about which to do.

The fact is, “regular” developpers are confused about this topic because, well, JS is confusing. People here in Github are not representative, ask around you what the differences between if (x), if (!!x), x == undefined, x === undefined, x == null, typeof x == "undefined" and x == void(0) are.

TS has an opportunity to improve the situation by prescribing a clear meaning to those types. Assume Type? to be a nullable type, which – as the name suggests – means T | null and param? to be an optional parameter/member, which can be undefined… it seems to me this is rather logical and could be applied without too much thinking by newcomers. And those people won’t try to mix and match both, which is a huge mistake.

Lack of symmetry between optional members and nullish types

There is a common theme of ? meaning “optional”. For a value, this means being nullable. For an identifier this means being undefined. Other than that, there should be no symmetry between x?: number and x: number? because they are not the same thing at all!

This is no more confusing than the upcoming this typing, where x(): number and x: () => number are going to have a different meaning…

What the real problem actually is

Reading other people’s opinion, especially from the TS team, I think that the real problem is that you can’t please everyone.

Today, JS usage in the wild is very varied. Many devs don’t even know about undefined or its difference with null and use them with no clues. Other devs make informed, deliberate decisions to use one or the other. It turns out your program will work in both cases.

TS support of null and undefined types will model JS faithfully and they will work well for everyone. 🎉

The problem is that we want syntactical sugar for our own use-case and this is not the same for everyone. For instance, I use T | null and I think it’s the “true” JS design, but if this is adopted, TS team (and others) will be unhappy because they won’t be able to use it in their existing code base and vice versa.

If TS tries to please everyone, the result will be a confusing mess. For example:

  • Having a set of hieroglyphs T?, T=, etc… This look cryptic and which is which? For newcomers: which one should I use? Let’s have fun and mix them in the same project (please don’t)!
  • Trying to extend the ? on other TS declarations to add undefined. This is a hackish, ugly and confusing workaround. It makes sense where it exists today (e.g. optional parameters), not so much in “optional local variables”?

Going this way is poor language design and won’t help new developers. Also, I think we should not try to come up with shorthands that cover every mix’n’match because a codebase should only use one single style.

Some ideas

  • We don’t introduce shorthands like T?. Every style is supported the same way but none is great. 😦
  • We support every style at once with T? = T | null | undefined. But this is poor design as it doesn’t reflect the real expected type of your code. When I do if (x !== null), I would like to have T not T | undefined (given it was declared x: T?). 😦
  • We don’t please everyone. We choose what we think future code should look like (whatever that is) and add built-ins helpers for others. Like add type Nil<T> = T | null; type Un<T> = T | undefined; type Void<T> = T | null | undefined (could be named better). So that alternate coding styles don’t feel too much left out, yet they are not first-class citizens.
  • We make the meaning of T? definable in tsconfig.json + in the .d.ts file pragma. I don’t think it’s elegant, but I think it’s the only sane way to please everyone.

Other ideas?

Say it does get adopted, but then a couple of months from today somebody (perhaps even you or myself) randomly comes up with a better notation - that would be a shame there wouldn’t really be any way to take it back.

If that were a valid reason not to introduce new syntax, then no new syntax would ever be able to be introduced, because you could always say “but what if something better comes up and we can’t undo it?”. Doesn’t seem very constructive.

(And null is a unit type, not a bottom type.)

Would become one of those places in the language where novices or people new or casual to it may tend to get confused because these semantics don’t seem to feel intuitively obvious.

A question mark after the identifier, x?: T, means the identifier may not actually exist or be set, so reading it may produce undefined.

A question mark after the type, x: T?, means the identifier will exist/be set, but the value itself is nullable, so reading it may produce null.

It’s pretty intuitive to me.

@mhegazy @RyanCavanaugh @weswigham @ahejlsberg

This question is directed to all Microsoft members who have participated or followed this thread:

You started the discussion specifically with the purpose of finding semantics for T? (or any of its variants), i.e. a shorthand type expression that would cause the receiving type to include either undefined or null or both in a union.

However, you already have an shorthand expression in the language to denote T | undefined:

interface MyInterface {
    optionalValue?: string;
}

function doSomething(optionalValue?: string);

Have you considered simply extending it to let, var and class property expressions (or others that I might have missed)?

let optionalValue?: string;
var optionalValue?: string;

class MyClass {
  optionalValue?: string;
}

Why wasn’t this proposed even as an option for consideration? Wouldn’t that be a natural and intuitive extension of the existing syntax?

Sure, this is not really a “complete” solution because it doesn’t allow expressing a type that, say, could be passed to a generic expression or set as a function return type (though that could be handled by type inference in some cases), as would be possible with a T? e.g.:

Array<number?>
function myFunction(): number?

but still it would cover a lot of common cases and may be worth considering, if only for its consistency and uniformity with the existing syntax of the language?

Example: It would give a nice symmetry between an interface declaration and its implementing class:

interface SomeInterface {
  optionalValue?: number;
}

class SomeClass implements SomeInterface {
  optionalValue?: number;
}

@basarat I don’t see the purpose of an optional class property. If you want a nullable class property you can use foo:number? = null. Nonetheless, if you want an optional property I don’t see why the foo?:number syntax couldn’t be permitted in classes as shorthand for foo: number | undefined.

Insofar as (new class { foo:number; }).foo being undefined is a problem, the problem is specifically a lack of checking for initialisation of properties, which is a distinct issue in and of itself.

but there are some cases where undefined is unavoidable

Of course, I do use undefiend to check existence of an optional parameter or optional property, but this is always in correspondence with a x?: T property/parameter declaration, so I don’t suspect I’ll need to write T | undefined as a type by itself very often. On the other hand, I will often need to write T | null for things that are nullable, so a shorthand for that would be handy.

I can only imagine needing undefined as a type if for some reason (not sure why) I want to be precise about the result of a void function, for which the type void unnecessarily includes null, which is unfortunate.

@aleksey-bykov sorry about that, was just trying to diffuse the strangeness of my proposal with some light-hearted comments.

I think the issue (I was trying to show) is no matter what T? means, at least one group of users wont find it useful, and its kind of hard to make syntax sugar for everyone without having it look a little ridiculous.

Maybe the original T? = T | null | undefined is the best after all. Its the longest union type (and therefore in most need of shortening) and for those situations where it needs to be more specific, T | undefined and T | null will do.

I’d like to make the following:

A modest proposal

  • ?T means T | undefined,
  • T? means T | null,
  • ?T? means T | null | undefined

The users that only use null will simply use T? whenever possible (except for optional arguments). They will almost never encounter things of type undefined. For them the only optional type that exists will be T?

The users that only use undefined will simply use ?T everywhere. They probably don’t use the DOM anyway if they manage to avoid T?. Therefore they get the same priority treatment as group 1 with a slightly different syntax.

The users who use both null and undefined, each where appropriate will be able to take the full advantage of this newfound type system expressiveness. The intuition of the question mark position will be used to guide them in choosing the correct type: if you squint a little, {t?:?number} looks like it only has a single question mark.

The users that don’t care about the differences between null or undefined will probably be slightly annoyed. To let them display their annoyance in a more expressive way T?? can be added. To further enhance expressiveness, a liberal number of “!” and “?” characters can be allowed between the two question marks, resulting with types such as T?!?, T??!?!? and others.

@malibuzios this is not compatible with ES6 default function arguments - according to spec, only undefined is recognised as a missing value but not null isn’t

@ahejlsberg

The semantics of T | null, T | undefined, and T | null | undefined are not in question and we’re not proposing any changes from what is described in the introduction to #7140.

I was glad to read that. I found the introduction to the PR perfectly fitting for the use-cases we have.

I vote for

2. T? means T | undefined.

We do not use null anywhere in the code-base. I don’t quite like adding ?? as an additional operator. The longer union type notation can be used if necessary.

Goes without saying big 👍

Note that the compiler source code seems to treat nullability and optionality as the same concept (by using undefined only and no null). Or perhaps nullability is not a concept that’s used at all ¯\_(ツ)_/¯ Just look at all the return undefined statements in : https://github.com/Microsoft/TypeScript/blob/01c329c05ed52bd13ff1ba48bc673c145ebc9a8f/src/compiler/utilities.ts

The return signatures for these would be T | undefined (since null is not used).

Also a some functions in that file take undefined as a valid argument without actually saying that the argument is optional. Optional arguments are a concept we already had in TypeScript argument signatures but were not used here => concepts of nullability / optionality were merged.

Also lots of JavaScript code bases use both null and undefined (when people try to differentiate the two concepts they end up using both) and might be painful to get these right every single time 🌹

I like @RyanCavanaugh’s proposal of T? = T | null. That’s the type I usually want when I want something “nullable”.

I don’t think it’s good to use one character for | null | undefined. I’ve seen many bugs resulting from undefined sneaking into a “nullable” parameter or property, where there probably just wasn’t any thought put into whether undefined should be allowed. The typical scenario:

  • There is a type/method X with a “nullable” property/parameter p
  • Many producers/callers of X may produce p: null, but there’s one obscure place where it may be produced with p: undefined
  • Most consumers/implementors of X can handle either p: null or p: undefined, but there’s one obscure place which breaks with p: undefined
  • The two incompatible sides never meet during testing

@tejacques your first concern seems to be covered by the PR (non-nullable variables cannot be used before they’re assigned a value)

I think it comes down to the question:

Is there any gain in forcing that a value (an argument or an object field) must be present but is allowed to be null ?

The only thing I can think of is high performance code, i.e. its beneficial to force object field presence to avoid extra hidden classes and to enforce monomorphism: https://jsperf.com/monomorphism-with-nullables-3

@tejacques I am not buying the argument about code being more standardized by embracing one notation or another, because at the end of the day it’s a union type, which means you can alias it

Nullable<T> = T | null 

in one library, then

Opt<T> = null | T

in the other library, and then go plain

var valute: null | T;

in the third library

and guess what? all 3 of them are going to be compatible as far as the contract

Regarding Ryan’s comments about the prevalence of null: Google’s JavaScript style is to use null (and not undefined) as the sentinel value. We have ~2.5x as many occurrences of “null” over “undefined” in our internal codebase across all products (excluding third-party JS). (I wanted to comment here to quantify how often T|null vs T|undefined appear in the code, but Closure’s type system makes that difficult to grep for.)

So on the purely selfish argument, I think options 2 and 3 aren’t useful for us. (We mix TS and JS code, translating types between TS and Closure types at the boundaries, so our TS code will surely also use nulls heavily.) I find Ryan’s argument pretty convincing so from the list I like option 5. (Or option 4: I think the majority of our uses of undefined are in default arguments, which TS has other syntax for already.)

I appreciate that the TS codebase style went the other way though! And while it’s possible to write type Opt<T> = T|undefined; I imagine you probably don’t want to actually do that to all of your code…

Because this bikeshed is purely about sugar, and the point of sugar is to huffman encode frequently-occurring patterns, I think your ultimate decision should be dictated by which things occur most frequently – you don’t necessarily need a short encoding of every possible permutation of types. For example, do you envision that DOM/browser APIs will be written with Nullable<T> sprinkled everywhere?

Maybe it’s worth skimming through DefinitelyTyped to see which libraries would benefit from what? https://github.com/DefinitelyTyped/DefinitelyTyped/search?utf8=✓&q=undefined+-filename%3Atests+-filename%3Atest&type=Code

@spion x?: number? syntax doesn’t work as well for standalone types

@spion relying on ?'s on initializers for one of the unions would mean there’s no shorthand for casting to that form.

Ok, so we now have five options:

  1. T? means T | null | undefined.
  2. T? means T | undefined.
  3. T? means T | undefined and T?? means T | null | undefined.
  4. T? means T | null.
  5. T? means T | null and T?? means T | null | undefined.

is this so important to come up with the consistent syntax? let’s face it there are 3 cases to address:

  • T | null
  • T | undefined
  • T | null | undefined

and then what about this:

  • null | undefined

can we just keep it the way it is:

  • to keep it consistent with the current syntax and semantics, let’s leave optional values value?: T the way the are T | null | undefined
  • for everything else let’s not make up any more syntax and use the union types or aliases: var value: string | null | undefined; or type Nullable<T> = T | null;

if you ask me the var value: T | null | undefined is as clear as a day, whereas var value? : T?? is what I type when I am hungover

the argument that T | null | undefined is longer to type is faint whoever wants can make it as short as

type u = undefined;
type n = null;
var value: T | u | n; // <-- fixed!

// or

type un = undefined | null;
var value: T | un;

Just to explore what this means

Option 1: T? = T | undefined

This is an argument for T? being exactly equal to T | undefined.

Consider some code:

declare function f(x?: number): void;
declare function g(x: number?): void;

Should f and g be identical in behavior when invoked with one argument ? Intuition seems to point to yes. If null were in the domain for x in g, then these functions would not be equivalent, which is potentially very confusing.

Now let’s think of the implementation of g:

function g(x: number?) {
  if (g === undefined) {
    // do something
  } else {
    // Safe to invoke a method on `g` ?
    console.log(g.toFixed());
  }
}

Is this code correct? Intuition, at least among people who don’t use null, says “yes”.

Similarly, for types:

interface Thing {
  height?: number;
  weight: number?;
}
let x: Thing = ...;
let h = x.height;
let w = x.weight;

If null were in the domain of number?, then h could be undefined or number, but w could be undefined, number, or null. Again, all things equal, symmetry is very preferred here.

Pros

  • Preserves symmetry between (x: number?) => void and (x?: number) => void
  • Makes things very smooth for programs that don’t use null
  • undefined is the only true “missing” value in JavaScript (modulo some obscure DOM APIs)

Cons

  • Makes things more awkward for programs that use null as a sentinel non-value

Option 2: T? = T | undefined | null

This is an argument for T? being exactly equal to T | undefined | null.

Consider some code:

function f(): number? {
  return Math.random() > 0.5 ? 32 : null;
}
const x = f();
Debug.assert(x !== undefined);

Is this code correct? It certainly looks like it. But if T? is T | undefined, then this program has two errors - f has an incorrect return null codepath, and x can’t be compared with undefined.

Also consider this code:

interface Thing {
  name?: string;
  value: string?;
}
// OK
var x: Thing = { value: null };
// Also OK
var y: Thing = { name: undefined, value: null };
// Error
var z: Thing = { name: null, value: null };

Again, at inspection, this program looks good.

Pros

  • Makes it easy to write programs that use null

Cons

  • Complete lack of symmetry between (x: number?) => void and (x? number) => void

Option 1a: Include T?? = T | undefined | null

If T? = T | undefined, then we might want an alias for T | undefined | null; given available punctuation, ?? seems like the best choice (T~ and T* were mentioned but considered generally worse).

Pros

  • Convenient
  • Generally easy to understand
  • Much shorter than Nullable<T>

Cons

  • T?? looks somewhat silly, at least for now
  • T?? isn’t the same as (T?)?
  • Maybe no one uses this and it’s a waste of effort / complexity