microsoft/TypeScript

Suggestion: non-nullable type

fdecampredon opened this issue ยท 358 comments

Introduce two new syntax for type declaration based on JSDoc

var myString: !string = 'hello world'; //non-nullable
var myString1: ?string = 'hello world'; // nullable 
var myString2: string = 'hello world'; // nullable 
var myString3 = 'hello world'; // nullable

by default type are nullable.

Two new compiler flag :

  • inferNonNullableType make the compiler infer non-nullable type :
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (I guess there might be a better name) :
var myString: !string = 'hello world'; // non-nullable
var myString1: string = 'hello world'; // non-nullable 
var myString2: ?string = 'hello world'; // nullable 
var myString3 = 'hello world' // non-nullable

I suggest using a type other than string as an example since it by nature is nullable. :P
I can perceive non-nullable types being problematic since the user and compiler of "!" expects the type to always be non-null, which can never be truly asserted in JavaScript. A user might define something like this:

function(myNonNull:!myClass):void {
  myNonNull.foo();
}

And because it's defined as non-null, everything might be happy for the compiler, but then someone else who uses it in javascript passes something null and kaboom.

Now that said, maybe the solution could be that for public facing methods, it could automatically assert not null. But then the compiler could also assert that you cannot have public properties (or private for that matter really) that can have a !nonnull declaration since they could not be enforced.

This may go deeper into the discussion of code contracts for this to be properly enforced.

Forgive my critics, I think there is very little need in non-nullable types if/as-soon-as algebraic data types are here. The reason people use null to represent a missing value is because there is no better way to do that in JavaScript and in most OOP languages alike. So imaging ADTs are already here. Then, as for the old libs written before non-nullables, having them won't make life any better. As for the new libs, with ADT's in place one can very accurately model what a value can take according to the business domain specification without using nulls at all. So I guess what I am saying is that ADT is way more powerful tool to address the same problem.

Personally, I just wrote a little Maybe<T> interface and use discipline to ensure that variables of that type are never null.

I suggest using a type other than string as an example since it by nature is nullable. :P
I can perceive non-nullable types being problematic since the user and compiler of "!" expects the type to always be non-null, which can never be truly asserted in JavaScript. A user might define something like this:

function(myNonNull:!myClass):void {
myNonNull.foo();
}
And because it's defined as non-null, everything might be happy for the compiler, but then someone else who uses it in javascript passes something null and kaboom.

I don't really understand you can also define :

function myFunc(str: string): int {
 return str && str.length;
}

and if someone pass an int to that function it will ends up with an error also, an advantage of typescript is to delegate to the compiler pass things that you would check manually in javascript, having another check for nullable/non-nullable type seems reasonable for me. By the way SaferTypeScript and ClosureCompiler already do that sort of check.

With union types, we could have a pretty simpler specification for that.
Let's say we have now a basic type 'null', we can have a 'stricter' mode where 'null' and 'undefined' is not compatible with any type, so if we want to express a nullable value we would do :

var myNullableString: (null | string);
var myString = "hello";
myNullableString = myString //valid
myString = myNullableString // error null is not assignable to string;

With the 'strict mode' activated typescript should check that every variable non nullable is initialized, also by default optional parameter are nullable.

var myString: string; // error
var myNullableString: (null | string); // no error

function myString(param1: string, param2?: string) {
  // param1 is string
  // param2 is (null | string)
}

@fdecampredon +1

IIRC from what Facebook showed of Flow which is using TypeScript syntax but with non-nullable types by default they support a shorthand for (null | T) as in your original post - I think it was ?T or T?.

var myString: string; // error

That could potentially be quite annoying in the case where you want to initialize a variable conditionally, eg.:

var myString: string;
if (x) {
myString = a;
} else if (y) {
myString = b;
} else {
myString = c;
}

In Rust for example, this is fine as long as the compiler can see that myString will get initialized before it is used but TypeScript's inference doesn't support this at the moment.

Honestly doing something like var myString = '' instead of var myString: string does not bother me so much, but sure that kind of rule is always possible.

@fdecampredon +1 for this - I like the idea very much. For code bases that are 100% JavaScript this would be a useful compile-time only constraint. (As I understand your proposal there's no intention for generated code to enforce this?)

As for shorthand for (null | string) sure ?string is fine.
And sure @johnnyreilly it's only a compile time check

Sum types make non-nullable types by default a very interesting possibility. The safety properties of non-nullable by default can't be overstated. Sum types plus the planned "if/typeof destructuring" (not sure what this should be called) even make it type safe to integrate nullable and non-nullable APIs.

However, making types non-nullable by default is a huge breaking change, which would require changing almost every existing third-party type definition file. While I am 100% for the breaking change, no one person is able to update the type definitions that are out there in the wild.

It's good that a great consensus of these definitions are collected in the DefinitelyTyped repo, but I still have practical concerns about this feature.

@samwgoldman the idea is to have non-nullable types only under a special compiler flag like nonImplicitAny this flag could be named strict or nonNullableType. So there would be no breaking changes.

@fdecampredon What about the type definitions for non-TypeScript libraries, like those at DefinitelyTyped? Those definitions are not checked by the compiler, so any 3rd party code that could return null would need to be re-annotated in order to work correctly.

I can imagine a type definition for a function that is currently annotated as "returns string," but sometimes returns null. If I depended on that function in my nonNullableType'ed code, the compiler doesn't complain (how could it?) and my code is no longer null-safe.

Unless I'm missing something, I don't think this is functionality that can be turned on and off with a flag. It seems to me that this is an all-or-nothing semantic change to ensure interoperability. I would be happy to be proven wrong, though, because I think a flag-switched feature is more likely to happen.

As an aside, there isn't much information available yet on Facebook's Flow compiler, but from the video recording of the presentation, it seems like they went with non-nullable by default. If so, at least there is some precedence here.

Ok let's assume there is a shorthand ? type for type | null | undefined.

@fdecampredon What about the type definitions for non-TypeScript libraries, like those at DefinitelyTyped? Those definitions are not checked by the compiler, so any 3rd party code that could return null would need to be re-annotated in order to work correctly.

I can imagine a type definition for a function that is currently annotated as "returns string," but sometimes returns null. If I depended on that function in my nonNullableType'ed code, the compiler doesn't complain (how could it?) and my code is no longer null-safe.

I don't see the problem, sure some definition files won't be valid with the nonNullableType mode, but most of the time good library avoid to return null or undefined so the definition will still be correct with majority of the cases.
Anyway I personally rarely can pick a DefinitelyTyped definition without having to check/modify it you'll just have a little bit of extra work to add a ? prefix with some definitions.

Unless I'm missing something, I don't think this is functionality that can be turned on and off with a flag. It seems to me that this is an all-or-nothing semantic change to ensure interoperability. I would be happy to be proven wrong, though, because I think a flag-switched feature is more likely to happen.

I don't see why we could not have a flag switched feature, the rules would be simple :

  • in normal mode ? string is equivalent to string and null or undefined are assignable to all the types
  • in nonNullableType mode ? string is equivalent to string | null | undefined and null or undefined are not assignable to any other type than null or undefined

Where is the incompatibility with a flag-switched feature ?

Flags that change the semantics of a language are a dangerous thing. One problem is that the effects are potentially very non-local:

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

It's important that someone looking at a piece of code can "follow along" with the type system and understand the inferences that are being made. If we starting having a bunch of flags that change the rules of the language, this becomes impossible.

The only safe sort of thing to do is to keep the semantics of assignability the same and change what's an error vs what isn't depending on a flag, much like how noImplicitAny works today.

I know it would break retro-compatibility, an I understand @RyanCavanaugh point of view, but after tasting that with flowtype it is honestly really an invaluable feature, I hope it will ends up being a part of typescript

In addition to RyanCavanaugh's comment --> From what I read somewhere, the ES7 specification / proposal mention the use of function overloading (Same function name but different input parameter datatype). That is a very sorely needed feature for Javascript.

From the flow docs:

Flow considers null to be a distinct value that is not part of any other type

var o = null;
print(o.x); // Error: Property cannot be accessed on possibly null value

Any type T can be made to include null (and the related value undefined) by writing ?T

var o: ?string = null;
print(o.length); // Error: Property cannot be accessed on possibly null or undefined value

[Flow] understands the effects of some dynamic type tests

(i.e. in TS lingo understands type guards)

var o: ?string = null;
if (o == null) {
  o = 'hello';
}
print(o.length); // Okay, because of the null check

Limitations

  • Checks on object properties are limited because of the possibility of aliasing:

    In addition to being able to adjust types of local variables, Flow can sometimes also adjust types of object properties, especially when there are no intermediate operations between a check and a use. In general, though, aliasing of objects limits the scope of this form of reasoning, since a check on an object property may be invalidated by a write to that property through an alias, and it is difficult for a static analysis to track aliases precisely

  • Type guard-style checks can be redundant for object properties.

[D]on't expect a nullable field to be recognized as non-null in some method because a null check is performed in some other method in your code, even when it is clear to you that the null check is sufficient for safety at run time (say, because you know that calls to the former method always follow calls to the latter method).

  • undefined is not checked.

Undefined values, just like null, can cause issues too. Unfortunately, undefined values are ubiquitous in JavaScript and it is hard to avoid them without severely affecting the usability of the language. For example, arrays can have holes for elements; object properties can be dynamically added and removed. Flow makes a tradeoff in this case: it detects undefined local variables and return values, but ignores the possibility of undefined resulting from object property and array element accesses

What if the option is added at the same time when introducing the null type (and the questionmark shorthand)? The presence of a null type in a file would force the compiler into non-nullable mode for that file even if the flag is not present at the command line. Or is that a bit too magical?

@jbondc seems good. however the problem with that is that it will ends up with ! everywhere :p

It's tempting to want to change JavaScript but the reality is a 'string' is nullable or can be undefined.

What does this mean? There are no static types in js. So, yes, strings are "nullable", but let's not forget that they are also numberable and objectable and fooable, etc. Any value can have any type.

So when layering a static type system on top of javascript, choosing whether static types are nullable or not is just a design decision. It seems to me non-nullable types are a better default, because it's usually only in special cases that you want a function signature, for instance, to accept a null value in addition to the type specified.

Directives like "use strict" that cause scoped changes to semantics are already a part of the language; I think it would be reasonable to have a "use nonnullable types" directive in TypeScript.

@metaweta I don't think it's enough, for example what happens if a non null module consume a nullable one :

//module A
export function getData(): string[] {
  return null;
}
//module B
'use nonnull'
import A = require('./A');

var data: string[] = A.getData();

data in module B is in fact nullable, but since 'use nonnull' was not used in module A should we report an error ?
I don't see a way to solve that problem with directive based feature.

Yes,

var data: string[] = A.getData();

would cause an error. Instead, you'd have to provide a default value for when getData() returns null:

var data: string[] = A.getData() || [];

@metaweta ok but how do you know that it's an error ? :)
type of getData is still '() => string[]' would you automaticly treat everything that comes from a 'nullable module' as 'nullable ' ?

Yes, exactly (unless a type from the nullable module is explicitly marked otherwise).

That sounds like you now want a per file flag that dictates whether of not that file defaults to nullable or not.

Personally I think it's a bit late to introduce this change, and @RyanCavanaugh is right, the change would make Typescript less predictable as you would not be able to determine what was going on just by looking at a file.

Do projects start with this compiler flag on or off by default? If someone is working on a no default nullable project and create / switch to a default nullable one will that cause confusion?
I currently work with No Implicit Any in most of my projects, and whenever I come across a project that doesn't have that option turned on it takes me by surprise.

The no impicit any is good, but in terms of flags that change the way the language behaves, I think that should be the line. Any more than that and people who are working on multiple projects started by different people with different rules are going to lose a lot of productivity due to false assumptions and slip ups.

@RyanCavanaugh was concerned about non-locality, and directives are lexically scoped. You can't get any more local unless you annotate each site. I'm not particularly in favor of the directive; I was just pointing out that the option exists and that it's at least as reasonable as "use strict" in ES5. I'm personally in favor of non-nullable types by default, but practically, it's too late for that. Given those constraints, I'm in favor of using ! somehow. @jbondc 's proposal lets you distinguish null from undefined; given that Java backends continue to make people use both values, it seems the most useful to me.

I'm sorry if I wasn't clear, I was both agreeing with Ryan and adding my own concerns.

Honestly if adding use not-null is the price for avoiding all the null pointer exception I would pay it without any problem, considering null or undefined as assignable to any type is the worse error that typescript made in my opinion.

@jbondc I have not used 'use strict' and am therefore making some assumptions, please correct me if my assumptions are wrong:

Not null does not affect the syntax that the programmer writes, but the capabilities of the next programmer that tries to use that code (assuming that creator and user are separate people).

So the code:

function myfoo (mynumber: number) {
    return !!mynumber;
} 

(typing on a phone so may be wrong)
Is valid code in both a normal project and a notnull project. The only way that the coder would know whether or not the code is working is if they look at the command line arguments.

At work we have a testing project (which includes prototyping new features) and a main project (with our actual code). When the prototypes are ready to be moved from one project to another (typically with large refractors), there would be no errors in the code, but errors in the use of the code. This behaviour is different to no implicit any and use strict which both would error immediately.

Now I have a fair amount of sway in these projects, so I can warn the people in charge to not use this new 'feature' because it wouldn't save time, but I don't have that capacity over all of the projects at work.
If we want to enable this feature in even one project, then we have to enable it in all of our other projects because we have a very significant amount of code sharing and code migration between projects, and this 'feature' would cause us a lot of time going back and 'fixing' functions that were already finished.

Right @jbondc. @Griffork: Sorry I didn't catch that misunderstanding; a directive is a literal string expression that appears as the first line of a program production or function production and its effects are scoped to that production.

"use not-null";
// All types in this program production (essentially a single file) are not null

versus

function f(n: number) {
  "use not-null";
  // n is not null and local variables are not null
  function g(s: string) {
    // s is not null because g is defined in the scope of f
    return s.length;
  }
  return n.toFixed(2);
}

function h(n: number) {
  // n may be null
  if (n) { return n.toFixed(3); }
  else { return null; }
}

Non-nullable types are useless. Non-nullable types are useles. They are useless. Useless! You don't realize it, but you don't really need them. There is very little sense in restricting yourself by proclaiming that from now on we are not going to be using NULL's. How would you represent a missing value, for example in a situation when you are trying to find a substring that is not there? Not being able to express a missing value (what NULL does now for now) isn't going to solve your problem. You will trade a harsh world with NULL's everywhere for equally harsh one with no missing values at all. What you really need is called algebraic data types that (among many other cool things) feature the ability to represent a missing value (what you are looking for in the first place and what is represented by NULL in imperative world). I am strongly against adding non-nullables to the language, because it looks like useless syntactic/semantic trash that is a naive and awkward solution to a well-known problem. Please read about Optionals in F# and Maybe in Haskell as well as variants (aka tagged unions, discriminated unions) and pattern matching.

@Aleksey-Bykov It sounds like you're unaware that JavaScript has two nullish values, undefined and null. The null value in JavaScript is only returned on a non-matching regexp and when serializing a date in JSON. The only reason it's in the language at all was for interaction with Java applets. Variables that have been declared but not initialized are undefined, not null. Missing properties on an object return undefined, not null. If you explicitly want to have undefined be a valid value, then you can test propName in obj. If you want to check whether a property exists on the object itself rather than if it's inherited, use obj.hasOwnProperty(propName). Missing substrings return -1: 'abc'.indexOf('d') === -1.

In Haskell, Maybe is useful precisely because there's no universal subtype. Haskell's bottom type represents non-termination, not a universal subtype. I agree that algebraic data types are needed, but if I want a tree labeled by integers, I want every node to have an integer, not null or undefined. If I want those, I'll use a tree labeled by Maybe int or a zipper.

If we adopt a "use not-null" directive, I'd also like "use not-void" (neither null nor undefined).

If you want to guarantee your own code from nulls just prohibit the null
literals. It's way easier than developing non-nullable types. Undefined is a
little bit more complicated, but if you know what they are coming from then
you know how to avoid them. Bottom in Haskell is invaluable! I wish
JavaScript (TypeScript) had a global super type without a value. I miss it
badly when I need to throw in an expression. I've been using TypeScript
since v 0.8 and never used nulls let alone had a need for them. Just ignore
them like you do with any other useless language feature like with
statement.

@Aleksey-Bykov If I'm writing a library and want to guarantee that inputs are not null, I have to do runtime tests for it everywhere. I want compile-time tests for it, it's not hard to provide, and both Closure and Flow provide support for non-null/undefined types.

@metaweta, you cannot guarantee yourself from nulls. Before your code is compiled there is a gazillion ways to make your lib cry: pleaseNonNullablesNumbersOnly(<any> null). After compiled to js there are no rules at all. Secondly, why would you care? Say it loud and clear upfront nulls are not supported, you put a null you will get a crash, like a disclaimer, you cannot guarantee yourself from all sort of people out there, but you can outline of your scope of responsibilities. Thirdly, I can hardly think of a major mainstream lib that is bulletproof to whatever user may put as input, yet it is still crazy popular. So is your effort worth troubles?

@Aleksey-Bykov If my library's clients are also type-checked, then I certainly can guarantee I won't get a null. That's the whole point of TypeScript. By your reasoning, there's no need for types at all: just "say loud and clear" in your documentation what the expected type is.

Off topic, nulls are extremely valuable for us because checking them is faster than checking against undefined.
While we don't use them everywhere, we try to use them where possible to represent uninitialised values and missing numbers.

On topic:
We've never had an issue with nulls 'escaping' into other code, but we have had issues with random undefinedes or NaNs appearing. I believe that careful code management is better than a flag in this scenario.

However, for library typings it would be nice to have the redundant type null so that we can choose to annotate functions that can return null (this should not be enforced by the compiler, but by coding practices).

@metaweta, by my reasoning your clients should not use nulls in their code base, it's not that hard, do a full search for null (case sensitive, whole word) and delete all of them. Too clunky? Add a compiler switch --noNullLiteral for fanciness. Everything else stays intact, same code, no worries, way lighter solution with minimal footprint. Back to my point, suppose your non-nullable types found their way to TS and avialable in 2 different flavors:

  • one can use ! syntax to denote a type that cannot take a null, for example string! cannot take a null
  • noNullsAllowed switch is on

then you get a piece of json from your server over ajax with nulls everywhere, moral: the dynamic nature of javascript cannot be fixed by a type annotation on top of it

@Aleksey-Bykov By the same token, if I'm expecting an object with a numeric property x and I get {"x":"foo"} from the server, the type system won't be able to prevent it. That's necessarily a runtime error and an inescapable problem when using something other than TypeScript on the server.

If, however, the server is written in TypeScript and running on node, then it can be transpiled in the presence of a .d.ts file for my front end code and the type checking will guarantee that the server will never send JSON with nulls in it or an object whose x property is not a number.

@metaweta, non-nullable types sure would be another type safety measure, I am no questioning that, I am saying that by imposing some very basic discipline (avoiding null literals in your code) you can eliminate 90% of your problems without asking for any help from the compiler. Well, even if you have enough resources to enforce this measure, then you still won't be able to eliminate the rest 10% of the problems. So what is the question after all? I ask: do we really need it that bad? I don't, I learned how to live without nulls successfully (ask me how), I don't remember when I got a null reference exception last time (besides in data from our server). There are way cooler stuff I wish we had. This particular one is so insignificant.

Yes we do need this badly. See The billion dollar mistake by Hoare. The null pointer exception (NPE) is the most common error encountered in typed programming languages that don't discriminate nullable from non-nullable types. Its so common that Java 8 added Optional in a desperate attempt to battle it.

Modelling nullables in the type system is not just a theoretical concern, its a huge improvement. Even if you take great care to avoid nulls in your code, the libraries you use might not and therefore its useful to be able to model their data properly with the type system.

Now that there are unions and type guards in TS, the type system is powerful enough to do this. The question is whether it can be done in a backward-compatible way. Personally I feel that this feature is important enough for TypeScript 2.0 to be backwards-incompatible in this regard.

Implementing this feature properly is likely to point to code that is already broken rather than break existing code: it will simply point to the functions that leak nulls outside them (most likely unintentionally) or classes that don't properly initialize their members (this part is harder as the type system may need to make allowance for member values to be initialized in the constructors).

This is not about not using nulls. Its about properly modelling all the types involved. Infact this feature would allow the use of nulls in a safe way - there would be no reason to avoid them anymore! The end result would be very similar to pattern matching on an algebraic Maybe type (except it would be done with an if check rather than a case expression)

And this isn't just about null literals. null and undefined are structurally the same (afaik there are no functions/operators that work on one but not the other) therefore they could be modelled sufficiently well with a single null type in TS.

@metaweta,

The null value in JavaScript is only returned on a non-matching regexp and when serializing a date in JSON.

Not true at all.

  • Interaction with the DOM produces null:
  console.log(window.document.getElementById('nonExistentElement')); // null
  • As @Aleksey-Bykov pointed out above, ajax operations can return null. In fact undefined is not a valid JSON value:
 JSON.parse(undefined); // error
 JSON.parse(null); // okay
 JSON.stringify({ "foo" : undefined}); // "{}"
 JSON.stringify({ "foo" : null}); // '{"foo":null}'

NB: We can pretend that undefined is returned via ajax, because accessing a non-existent property will result in undefined - which is why undefined is not serialised.

If, however, the server is written in TypeScript and running on node, then it can be transpiled in the presence of a .d.ts file for my front end code and the type checking will guarantee that the server will never send JSON with nulls in it or an object whose x property is not a number.

This is not entirely correct. Even if the server is written in TypeScipt, one can in no way guarantee nulls from being introduced without checking every single property of every single object obtained from persistent storage.

I kind of agree with @Aleksey-Bykov on this. While it would be absolutely brilliant if we can have TypeScript alert us at compile time about errors introduced by null and undefined, I fear it will only induce a false sense of confidence and end up catching trivia while the real sources of null go undetected.

Even if the server is written in TypeScipt, one can in no way guarantee nulls from being introduced without checking every single property of every single object obtained from persistent storage.

This is in fact an argument for non-nullable types. If your storage can return null Foo's, then the type of the object retrieved from that storage is Nullable<Foo>, not Foo. If you then have a function that returns that is meant to return Foo, then you have to take responsibility by handling the null (either you cast it because you know better or you check for null).

If you didn't have non-nullable types you would not necessarily think to check for null when returning the stored object.

I fear it will only induce a false sense of confidence and end up catching trivia while the real sources of null go undetected.

What sort of non-trivia do you think non-nullable types will miss?

This is not entirely correct. Even if the server is written in TypeScipt, one can in no way guarantee nulls from being introduced without checking every single property of every single object obtained from persistent storage.

If the persistent storage supports typed data, then there would be no need. But even if that weren't the case, you'd have checks only at the data fetching points and then have a guarantee throughout all of your other code.

I kind of agree with @Aleksey-Bykov on this. While it would be absolutely brilliant if we can have TypeScript alert us at compile time about errors introduced by null and undefined, I fear it will only induce a false sense of confidence and end up catching trivia while the real sources of null go undetected.

Using nullable types wouldn't be an absolute requirement. If you feel that its unnecessary to model the cases where a method returns null as they're "insignificant", you could just not use a nullable type in that type definition (and get the same unsafety as always). But there is no reason to think that this approach will fail - there are examples of languages that have successfully implemented it already (e.g. Kotlin by JetBrains)

@Aleksey-Bykov Honestly you got it completely wrong, one of the best thing about non-nullable types is the possibility to express a type as nullable.
With your strategy of never using null to prevent null pointer error you completely loose the possibility of using null out of the fear of introducing error, that's completely silly.

Another thing please in a discussion about a language feature don't go with stupid comments like :

Non-nullable types are useless. Non-nullable types are useles. They are useless. Useless! You don't realize it, but you don't really need them.

That just make me feel like I should ignore whatever you will ever post anywhere on the web, we are here to discuss about a feature, I can understand and gladly accept that your point of view is not mine, but don't behave like a kid.

I am not against introducing non-null type annotation at all. It has been shown to be useful in C# and other languages.

The OP has changed the course of the discussion with the following:

Honestly if adding use not-null is the price for avoiding all the null pointer exception I would pay it without any problem, considering null or undefined as assignable to any type is the worse error that typescript made in my opinion.

I was merely pointing out the prevalence of null and undefined in the wild.

I should also add that one of the things that I truly appreciate about TypeScript is the laissez-faire attitude of the language. It has been a breath of fresh air.

Insisting that types are non-nullable by default goes against the grain of that spirit.

I've been seeing a number of arguments floating around as to why we do / don't need this and I want to see if I understand all of the underlying cases that have been discussed such far:

  1. want to know whether or not a function can return null (caused by it's execution pattern, not it's typing).
  2. want to know if a value can be null.
  3. want to know if a data object can contain null values.

Now, there are only two cases in which situation 2 can occur: of you're using nulls or if a function returns a null. If you eliminate all nulls from your code (assuming that you don't want them) then really situation 2 can only occur as a result of situation 1.

Situation 1 I think is best solved by annotating the function's return type to show presence of a null value. This does not mean that you need a non null type. You can annotate the function (for example by using union types) and not have non null types, it's just like documentation, but probably clearer in this case.

Solution 2 is also solved by this.

This allows programmers working at their company to use processes and standards to enforce that null types are marked up, and not the Typescript team (exactly the same way that the whole typing system is an opt-in so would the explicit nullable types be an opt in).

As for scenario 3, the contract between the server and the client is not for Typescript to enforce, being able to mark-up the affected values as possibly null might be an improvement, but eventually you'll get the same garentee from that as typescript's tooling gives you on every other value (which is to say, none unless you have good coding standards or practices!

(posting from phone, sorry for errors)

@fdecampredon, it's not the fear in the first place, it's that using null is unnecessary. I don't need them. As a nice bonus I got a problem of null reference exceptions eliminated. How is it all possible? By employing a sum-type with an empty case. Sum-types are a native feature of all FP languages like F#, Scala, Haskell and together with product types called algebraic data types. Standard examples of sum types with an empty case would be Optional from F# and Maybe from Haskell. TypeScript doesn't have ADT's, but instead it has a discussion in progress about adding non-nullables that would model one special case of what ADT's would have covered. So my message is, ditch the non-nullables for ADT's.

@spion, Bad news: F# got nulls (as legacy of .NET). Good news: no one uses them. How can you not use null when it's there? They have Optionals (just like the most recent Java as you mentioned). So you don't need null if you have a better choice at your disposal. This is what I am ultimately suggesting: leave nulls (non-nulls) alone, just forget they exist, and implement ADT's as a language feature.

We use null but not the same way you people use. My company source code comes in 2 parts.

  1. When it comes to data (like database data), we replace null with blank data at the time of variable declaration.

  2. All others, like programmable objects, we use null so we know when there's a bug and loophole in source code where objects aren't created or assignable that aren't necessary javascript objects. The undefined is javascript object issues where there's a bug or loophole in the source code.

The data we don't want to be nullable cuz it is customer data and they'll see null wordings.

@Aleksey-Bykov typescript has adt with union type, the only thing missing is pattern matching but that's a feature that just cannot be implemented with the typescript philosophy of generating javascript close to the original source.

On the other end it's impossible to define whith union type the exclusion of null value, that's why we need non-null type.

@fdecampredon, TS doesn't have ADT's, it has unions which are not sum types, because, as you said, 1. they cannot model an empty case correctly since there is no unit type, 2. there is no reliable way to destructure them

pattern matching for ADT's can be implemented in a way that is aligned closely with generated JavaScript, anyway I hope that this argument isn't a turning point

@Aleksey-Bykov this is not F#. Its a language that aims to model the types of JavaScript. JavaScript libraries use null and undefined values. Therefore, those values should be modeled with the appropriate types. Since not even ES6 supports algebraic data types, it doesn't make sense to use that solution given TypeScript's design goals

Additionally, JavaScript programmers typically use if checks (in conjuction with typeof and equality tests), instead of pattern matching. These can already narrow TypeScript union types. From this point its only a tiny step to support non-nullable types with benefits comparable to algebraic Maybe etc.

I'm surprised that nobody actually mentioned the huge changes that lib.d.ts may need to introduce and potential problems of the transient null state of class fields during constructor invocation. Those are some real, actual potential issues to implement non-nullable types...

@Griffork the idea is to avoid having null checks everywhere in your code. Say you have the following function

declare function getName(personId:number):string|null;

The idea is that you check whether the name is null only once, and execute all the rest of the code free from worries that you have to add null checks.

function doSomethingWithPersonsName(personId:number) {
  var name = getName(personId);
  if (name != null) return doThingsWith(name); // type guard narrows string|null to just string
  else { return handleNullCase(); }
}

And now you're great! The type system guarantees that doThingsWith will be called with a name that is not null

function doThingsWith(name:string) {
  // Lets create some funny versions of the name
  return [uppercasedName(name), fullyLowercased(name), funnyCased(name)]
}

None of these functions need to check for a null, and the code will still work without throwing. And, as soon as you try to pass a nullable string to one of these functions, the type system will tell you immediately that you've made an error:

function justUppercased(personId:number) {
  var name = getName(personId);
  return uppercasedName(name); // error, passing nullable to a function that doesn't check for nulls.
}

This is a huge benefit: now the type system tracks whether functions can handle nullable values or not, and furthermore, it tracks whether they actually need to or not. Much cleaner code, less checks, more safety. And this is not just about strings - with a library like runtime-type-checks you could also build type guards for much more complex data

And if you don't like the tracking because you feel that its not worth modeling the possibility of a null value, you can revert back to the good old unsafe behavior:

declare function getName(personId:number):string;

and in those cases, typescript will only warn you if you do something that is obviously wrong

uppercasedName(null);

I frankly don't see downsides, except for backward-compatibility.

@fdecampredon Union types are just that, unions. They are not disjoint unions a.k.a. sums. See #186.

@Aleksey-Bykov

Note that adding an option type is still going to be a breaking change.

// lib.d.ts
interface Document {
    getElementById(id: string): Maybe<Element>;
}

...

// Code that worked with 1.3
var myCanvas = <HTMLCanvasElement>document.getElementById("myCanvas");
// ... now throws the error that Maybe<Element> can't be cast to an <HTMLCanvasElement>

After all, you can get a poor man's option types right now with destructuring

class Option<T> {
    hasValue: boolean;
    value: T;
}

var { hasValue, myCanvas: value } = <Option<HTMLCanvasElement>> $("myCanvas");
if (!hasValue) {
    throw new Error("Canvas not found");
}
// Use myCanvas here

but the only value from this is if lib.d.ts (and any other .d.ts, and your whole codebase but we'll assume we can fix that) also use it, otherwise you're back to not knowing whether a function that doesn't use Option can return null or not unless you look at its code.

Note that I also am not in favor of types being non-null by default (not for TS 1.x anyway). It is too big a breaking change.

But let's say we're talking about 2.0. If we're going to have a breaking change anyway (adding option types), why not make types non-nullable by default as well? Making types non-nullable by default and adding option types is not exclusive. The latter can be standalone (eg. in F# as you point out) but the former requires the latter.

@Arnavion, there is some misunderstanding, I didn't say we need to replace the signatures, all existing signatures stay intact, it's for the new developments you are free to go either with ADT or whatever else you want. So no breaking changes. Nothing is being made non-null by default.

If ATDs are here, it's up to a developer to wrap all places where nulls can leak into the application by transforming then into optionals. This can be an idea for a standalone project.

@Aleksey-Bykov

I didn't say we need to replace the signatures, all existing signatures stay intact

I said that the signatures need to be replaced, and I gave the reason already:

but the only value from this is if lib.d.ts (and any other .d.ts, and your whole codebase but we'll assume we can fix that) also use it, otherwise you're back to not knowing whether a function that doesn't use Option can return null or not unless you look at its code.


Nothing is being made non-null by default. Ever.

For TS 1.x, I agree, only because it is too big a breaking change. For 2.0, using option type in the default signatures (lib.d.ts etc.) would already be a breaking change. Making types non-nullable by default in addition to that becomes worth it and carries no downsides.

I disagree, introducing optionals should not break anything, it's not like we either use optionals or nullables or non-nullables. Everyone uses whatever they want. Old way of doing things should not depend on new features. It's up to a developer to use an appropriate tool for his immediate needs.

So you're saying that if I have a function foo that returns Option<number> and a function bar that returns number, I'm not allowed to be confident that bar cannot return null unless I look at the implementation of bar or maintain documentation "This function never returns null."? Don't you think this punishes functions which will never return null?

function bar from your example was known as nullable from the begging of time, it was used in some 100500 applications all around and everyone treated the result as nullable, now you came around and looked inside of it and discovered that null is impossible, does it mean that you should go ahead and change the signature from nullable to non-nullable? I think you should not. Because this knowledge, although valuable, isn't worth braking 100500 applications. What you should do is to come up with a new lib with revised signatures that does it like this:

old_lib.d.ts

...
declare function bar(): number; // looks like can return a potentially nullable number
...

revised_lib.d.ts

declare function bar(): !number; // now thank to the knowledge we are 100% certain it cannot return null

now the legacy apps keep using the old_lib.d.ts, for new apps a developer is free to choose revised_libs.d.ts

Unfortunately revised_libs.d.ts has 50 other functions that I haven't looked at yet, all of which return number (but I don't know whether it's really number or nullable number). What now?

Well, take your time, ask for help, use versioning (depending on the level of knowledge you've gained so far you may want to release it graduately with ever increasing version number: revised_lib.v-0.1.12.d.ts)

It's not necessary actually. A function that returns nullable type in the signature but non-nullable type in the implementation only results in redundant error checking by the caller. It doesn't compromise safety. Gradually annotating more and more functions with ! as you discover them will work just fine, as you said.

I'm not a fan of ! only because it's more baggage to type (both in terms of keystrokes and needing to remember to use it). If we want non-nullability in 1.x then ! is one of the options already discussed above, but I would still say that having an eventual breaking change with 2.0 and making non-nullability the default is worth it.

On the other hand, maybe it'll lead to a Python 2/3-esque situation, where nobody upgrades to TS 2.0 for years because they can't afford to go through their million-line codebase making sure that every variable declaration and class member and function parameter and... is annotated with ? if it can be null. Even 2to3 (the Python 2 to 3 migration tool) doesn't have to deal with wide-ranging changes like that.

Whether 2.0 can afford to be a breaking change depends on the TS team. I would vote for yes, but then I don't have a million-line codebase that will need fixing for it, so maybe my vote doesn't count.

Perhaps we should ask the Funscript folks how they reconcile DOM API returning nulls with F# (Funscript uses TypeScript's lib.d.ts and other .d.ts for use from F# code). I've never used it, but looking at http://funscript.info/samples/canvas/index.html for example it seems the type provider does not think that document.getElementsByTagName("canvas")[0] can ever be undefined.

Edit: Here it seems document.getElementById() is not expected to return null. At the very least it doesn't seem to be returning Option<Element> seeing it is accessing .onlick on the result.

@spion
Thanks, I hadn't thought of that.

At work our codebase is not small, and this breaking change that people want would set us back a lot of time with very little gain. Through good standards and clear communication between our developers we've not had problems with nulls appearing where they shouldn't.
I am honestly surprised that some people are pushing for it so badly.
Needless to say this would give us very little benefit and would cost us a lot of time.

@Griffork have look at this filtered list of issues in the typescript compiler for an estimate on how big of a benefit this change could make. All of the "crash" bugs listed at that link could be avoided by using non-nullable types. And we're talking about the awesome Microsoft level of standards, communication and code-review here.

Regarding breakage, I think that if you continue using existing type definitions its possible that you wont get any errors at all, except the compiler pointing out potentially uninitialized variables and fields leaking out. On the other hand, you might get a lot of errors, particularly e.g. if class fields are often left uninitialized in constructors in your code (to be initialized later). Therefore I understand your concerns, and I'm not pushing for a backward-incompatible change for TS 1.x. I still hope that I've managed to persuade you that if any change to the language was worthy of breaking backward compatibility, its this one.

In any case, Facebook's Flow does have non-nullable types. Once its more mature, it might be worth investigating as a replacement of TS for those of us who care about this issue.

@spion, the only number your list gives us is how many times null or undefined were mentioned for any reason out there, basically it only says that null and undefined have been talked about, I hope it's clear that you cannot use it as an argument

@Aleksey-Bykov I am not - I looked at all the issues on that filtered list and every single one that had the word "crash" in it was related to a stack trace which shows that a function attempted to access a property or a method of an undefined or null value.

I tried to narrow the filter with different keywords and I (think I) managed to get all of them

@spion
Question: how many of those errors are caused in locations that they would need to mark the variable as nullable or undefinable though?

E. G. If an object can have a parent, and you initialise parent to null, and you're always going to have one object with a parent that is null, you will still have to declare the parent as possibly null.
The problem here is if a programmer writes some code with the assumption that a loop will always break before it reaches the null parent. That's not a problem with null being in the language, the exact same thing would happen with undefined.

Reasons for keeping null as easy to use as it is now:
โ€ข It's a better default value than undefined.
. 1) it's faster to check in some cases (our code must be very performant)
. 2) it makes for in loops work more predictably on objects.
. 3) it makes array usage make more sense when using nulls for blank values (as opposed to missing values). Note that delete array[i] and array[i] = undefined have different behaviour when using indexOf (and probably other popular array methods).

What I feel the result of making nulls require extra mark up to use in the language:
โ€ข I got an undefined error instead of a null error (which is what would happen in most of the typescript scenarios).

When I said we don't have a problem with nulls escaping, I meant that variables that are not initialised to null never become null, we still get null exception errors in exactly the same place we would get undefined errors (as does the Typescript team). Making null harder to use (by requiring extra syntax) and leaving undefined the same will actually cause more problems to some developers (e. g. us).

Adding extra syntax to use null means that for several weeks / months developers who use a lot of null will be making errors left, right and center while they try to remember the new syntax. And it will be another way to slip up in the future (by annotating something slightly incorrectly). [Time to point out that I hate the idea of using symbols to represent types, it makes the language less clear]

Until you can explain to me a situation in which null causes an error or problem that undefined would not, then I won't agree with making null significantly harder to use than undefined. It has it's use case, and just because it's not helping you, doesn't mean that the breaking change that you want (that will hurt the workflow of other developers) should go ahead.

Conclusion:
There is no point in being able to declare non-nullable types without being able to define non-undefined types. And non-undefined types are not possible due to the way javascript works.

@Griffork when I say non-nullable, I mean non-nullable-or-undefined. And its not true that its not possible due to the way JS works. With the new type guard features, once you use a type guard you know that the value flowing from there cannot be null or undefined anymore. There is a tradeoff involved there too, and I submit Facebook Flow's implementation as proof that its quite doable.

The only case that will become slightly harder to use is this one: I will temporarily assign null to this variable then I will use it in this other method as if it not null, but I know that I'll never call this other method before initializing the variable first, so there is no need to check for nulls. This is very brittle code, and I would definitely welcome the compiler warning me about it: its a refactor away from being a bug anyway.

@spion
I do believe that I'm finally understanding where you're coming from.

I believe that you're wanting type guards to help determine when a value cannot be null, and allow you to call functions that don't check for null within. And if the guarding if-statement is removed, then that becomes an error.

I can see that being useful.

I also believe that this isn't really going to be the silver bullet you're hoping for.

A compiler that does not run your code and test every facet of it is not going to be better at determining where undefineds/nulls are than the programmer who wrote the code. I am concerned that this change would lull people into a false sense of security, and actually make null/undefined errors more difficult to track when they do occur.
Really, I think the solution that you need is a good set of testing tools that support Typescript, that can reproduce these sort of bugs in Javascript, rather than implementing a type in a compiler that can not deliver on it's promise.

You mention Flow as having a solution to this problem, but when reading your link I saw some concerning things:

"Flow can sometimes also adjust types of object properties, especially when there are no intermediate operations between a check and a use. In general, though, aliasing of objects limits the scope of this form of reasoning, since a check on an object property may be invalidated by a write to that property through an alias, and it is difficult for a static analysis to track aliases precisely."

"Undefined values, just like null, can cause issues too. Unfortunately, undefined values are ubiquitous in JavaScript and it is hard to avoid them without severely affecting the usability of the language[...] Flow makes a tradeoff in this case: it detects undefined local variables and return values, but ignores the possibility of undefined resulting from object property and array element accesses."

Now undefined and null work differently, undefined errors can still show up everywhere, the question mark cannot guarantee that the value is not null, and the language behaves more differently to Javascript (which is what TS is trying to avoid from what I've seen).

p.s.

foo(thing: whatiknowaboutmyobject) {
    if (thing.hidden) {
        delete thing.description;
    }
}

if (typeof thing.description === "string") {
    //thing.description is non-nullable now, right?
    foo(thing);
    //What is thing.description?
    console.log(thing.description.length);
}

TS is already vulnerable to aliasing effects (as is any language that allows mutable values). This will compile without any errors:

function foo(obj: { bar: string|number }) {
    obj.bar = 5;
}

var baz: { bar: string } = { bar: "5" };

foo(baz);

console.log(baz.bar.charAt(0)); // Runtime error - Number doesn't have a charAt method

The Flow docs are stating this only for completeness.

Edit: Better example.

Old:

Yes, but I argue that the means of setting something to undefined is much greater than the means of setting something to another value.

I mean,

mything.mystring = 5; // is clearly wrong.
delete mything.mystring; //is not clearly wrong - this is not quite the equivalent of setting mystring to >undefined.

Edit:
Meh, at this point it's pretty much personal preference. After using javascript for ages, I do not think this suggestion is going to help the language. I think it's going to lull people into a false sense of security, and I think that it will drive Typescript (as a language) away from Javascript.

@Griffork For an example of how the current typescript luls you into a false sense of security, try the example you presented in the playground:

var mything = {mystring: "5"}; 
delete mything.mystring;
console.log(mything.mystring.charAt(1));

By the way, the delete operator could be treated the same way as assigning a value of type null and that would be sufficient to cover your case too.

The claim that the language will behave differently than JavaScript is true, but meaningless. TypeScript already has behavior different than JavaScript. The point of a type system has always been to disallow programs that don't make sense. Modelling non-nullable types simply adds a couple of extra restrictions. Disallowing the assignment of null or undefined values to a variable of non-nullable type is precisely the same as disallowing the assignment of a number to a variable of type string. JS allows both, TS could allow neither

@spion

the idea is to avoid having null checks everywhere in your code

If I understand what you are advocating:

A. Make all types non-null by default.
B. Mark fields and variables that are nullable.
C. Ensure the application/library developer checks all entry points into the application.

But doesn't that mean the onus for ensuring one's code is free of nulls is on the person writing the code, and not on the compiler? We are effectively telling the compiler "dw, I'm not letting any nulls into the system."

The alternative is to say, nulls are everywhere, so don't bother, but if something is non-nullable then I'll let you know.

The fact is the latter approach is prone to null reference exceptions, but it's more truthful. Pretending that a field on an object obtained over the wire (i.e. ajax) is non-null implies having faith in God ๐Ÿ˜ƒ .

I believe there is strong disagreement on this issue because, depending on what one is working on, item C above could either be trivial or infeasible.

@jbondc I'm glad you asked that. Indeed CallExpression is marked as undefined or nullable. However, the type system currently does not take any advantage of that - it still allows all the operations on typeArguments as if it isn't null or undefined.

However, when using the new union types in combination with non-nullable types, the type could be expressed as NodeArray<TypeNode>|null. Then the type system will not allow any operations on that field unless a null check is applied:

if (ce.typeArguments != null) {
  callSomethingOn(ce.typeArguments)
}

// callSomethingOn doesn't need to perform any checks

function callSomethingOn(na:NodeArray<TypeNode>) {
...
}

With the help of TS 1.4 type guards, inside the if block the type of the expression will be narrowed to NodeArray<TypeNode> which in turn will allow all NodeArray operations on that type; additionally, all functions called within that check will be able to specify their argument type to be NodeArray<TypeNode> without performing any more checks, ever.

But if you try to write

function someOtherFunction(ce: CallExpression) {
  callSomethingOn(ce.typeArguments)
}

the compiler will warn you about it at compile time, and the bug simply wouldnt've happened.

So no, @NoelAbrahams, this is not about knowing everything for certain. Its about the compiler helping you tell what value a variable or field can contain, just like with all other types.

Of course, with external values, as always, its up to you to specify what their types are. You can always say that external data contains a string instead of a number, and the compiler wont complain yet your program will crash when trying to do string operations on the number.

But without non-nullable types, you don't even have the ability to say that a value can't be null. The null value is assignable to each and every type and you can't make any restrictions about it. Variables can be left uninitialized and you wont get any warnings since undefined is a valid value for any type. Therefore, the compiler is unable to help you catch null and undefined-related errors at compile time.

I find it surprising that there are so many misconceptions about non-nullable types. They're just types that can't be left uninitialized or can't be assigned the values null or undefined. This isn't that dissimilar to being unable to assign a string to a number. This is my last post on this issue, as I feel that I'm not really getting anywhere. If anyone is interested in finding out more, I recommend starting with the video "The billion dollar mistake" I mentioned above. The issue is well known and tackled by many modern languages and compilers successfully.

@spion, I do entirely agree about all the benefits of being able to state whether a type can or cannot be null. But the question is do you want types to be non-null _by default_?

Pretending that a field on an object obtained over the wire (i.e. ajax) is non-null implies having faith in God

So don't pretend. Mark it as nullable and you'll be forced to test it before using it as non-nullable.

Sure. It boils down to whether we want to mark a field as nullable (in a system where fields are non-null by default) or whether we want to mark a field as non-nullable (in a system where fields are nullable by default).

The argument is that the former is a breaking change (which may or may not be of significance) and also untenable because it requires the application developer to check and guarantee all entry points.

@NoelAbrahams I don't see why it's 'intenable' basically most of the time you don't want null, also when an entry point can return null you will have to check it, in the end a type system with non-null type as default will allows you to make less null check because you will be able to trust some api/library/entry point in your application.

When you think a bit about it marking a type as non null in a nullable type system has limited value, you will still be able to consume nullable typed variable/return type without being forced to test it.
It will also force definition author to write more code, since most of the time well designed library never return null nor undefined value.
Finally even the concept is strange, in a type system with non nullable type, a nullable type is perfectly expressible as an union type: ?string is the equivalent of string | null | undefined. In a type system with nullable type as default where you can mark type as non nullable how would you express !string ? string - null - undefined ?

In the end I don't really understand the concern of people here, null is not a string, in the same way than 5 is not a string, both value won't be able to be used where a string is expected, and letting slip var myString: string = null is as error prone as: var myString: string = 5.
Having null or undefined assignable to any type is perhaps a concept that developer are familiar with, but it is still a bad one.

I don't think I was entirely correct in my previous post: I'll blame it on the hour.

I've just looked through some of our code to see how things would work and it would certainly help to mark certain fields as nullable, for example:

interface Foo {
        name: string;
        address: string|null; /* Nullable */
}

var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.toString(); // Error: do not use without null check

But what I do object to is the following:

foo.name = undefined; // Error: non-nullable

I feel this will interfere with the natural way of working with JavaScript.

The exact same could apply with number :

interface Foo {
        name: string;
        address: string|number; 
}
var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.slice() // error

foo.name  = 5 // error

And it's still valid in JavaScript

Reasonably how many time do you willingly assign null to a property of an object ?

I think that most things would be marked as null but you'd be relying on type guards to declare that the field is now non nullable.

@fdecampredon
Quite a lot actually.

@Griffork,

I think that most things would be marked as null

That was my initial thought. But after going through some sections of our code I found comments such as the following:

interface MyType {

     name: string;

     /** The date the entry was updated from Wikipedia or undefined for user-submitted content. */
     wikiDate: Date; /* Nullable */
}

The idea that a field is nullable is often used to provide information. And TypeScript will catch errors if it requires a type guard when accessing wikiDate.

@fdecampredon

foo.name = 5 // error
And it's still valid in JavaScript

True, but that is an error because TypeScript knows with 100% certainty that it was not intentional.

whereas

foo.name = undefined; // Do not send name to server

is perfectly intentional.

I think the implementation that would most closely fit our requirements is to not use union types, but go with the original suggestion:

 wikiDate: ?Date;

I agree with @NoelAbrahams

foo.name = 5 // error
And it's still valid in JavaScript
True, but that is an error because TypeScript knows with 100% certainty that it was not intentional.

The compiler just know that you marked name as string and not string | number if you want a nullable value you would just mark it as ?string or string | null ( which is pretty much equivalent )

I think the implementation that would most closely fit our requirements is to not use union types, but go with the original suggestion:

wikiDate: ?Date;

So we are agree type are non null by default and you would mark nullable with ? :) .
Note that it would be an union type since ?Date would be the equivalent of Date | null | undefined :)

Oh sorry, I was trying to agree to nullable by default and not null with special typing (the symbols are confusing).

@fdecampredon, actually what it means is when a field or variable is marked as nullable then a type guard is required for access:

var wikiDate: ?Date;

wikiDate.toString(); // error
wikiDate && wikiDate.toString(); // okay

This is not a breaking change, because we should still be able to do this:

 var name: string;   // okay
 name.toString();  // if you think that's fine then by all means

Perhaps you believe that we can't have this without introducing null into union types?

Your first example is absolutely right when you do :

wikiDate && wikiDate.toString(); // okay

you use a typeguard and the compiler should not warn anything.

However your second example is not good

var name: string;   // okay
name.toString();  // if you think that's fine then by all means

the compiler should have an error here, a simple algorithm could just error on the first line (unitialized variable not marked as nullable), a more complex one could try to detect assignation before first usage :

var name: string;   // okay
name.toString();  // error because not initialized
var name: string;
if (something) {
  name = "Hello World";
} else {
  name = "Foo bar";
}
name.toString();  // no error since name will always be initialized.

I don't know exactly where to put the barrier but it would sure need some kind of subtile tunning to not get in the way of the developper.

It's a breaking change and cannot be introduced before 2.0, except perhaps with the 'use nonnull' directive proposed by @metaweta

Why not have:

var string1: string; //this works like typescript does currently, doesn't need type-guarding before use, null and undefined can be assigned to it.
string1.length; //works
var string2: !string; //this doesn't work because the string must be assigned to a non-null and non-undefined value, doesn't need type-guarding before use.
var string3: ?string; //this must be type guarded to non-null, non-undefined before use.
var string4: !string|null = null; //This may be null, but should never be undefined (and must be type-guarded before use).
var string5: !string|undefined; //this may never be null, but can be undefined (and must be type-guarded before use).

And have a compiler flag (that only works if -noimplicitany is on) which says -noinferrednulls, which disables the normal syntax for types (like string, and int) and you have to supply a ? or ! with them (null, undefined and any types being exceptions).

In this manner, non-nullables are an opt-in, and you can use the compiler flag to force a project to be explicity nulled.
The compiler flag errors at the assignment of types, not after (like the previous proposal).

Thoughts?

Edit: I wrote this because it forces the idea of using non-null to be explicit in every action. Anyone who reads the code who comes from any other TS project will know exactly what's happening. Also the compiler flag becomes very obvious if it's on (as blah: string is an error, but blah:!string isn't, similar to the way -noimplicitany works).

Edit2:
DefinatelyTyped could then be upgraded to support noninferrednulls, and they won't change the use of the libraries if people choose to not opt-in to the ? and ! feature.

I don't care whether non-null and non-undefined are opt-in, opt-out, with
type modifiers (!?), a directive, or a compiler flag; I'll do whatever it
takes to get them so long as they are possible to express, which is not
currently the case.

On Mon, Dec 22, 2014 at 2:35 PM, Griffork notifications@github.com wrote:

Why not have:

var string1: string; //this works like typescript does currently., doesn't need type-guarding before use, null and undefined can be assigned to it.
string1.length; //worksvar string2: !string; //this doesn't work because the string must be assigned to a non-null and non-undefined value, doesn't need type-guarding before use.var string3: ?string; //this must be type guarded to non-null, non-undefined before use.var string4: !string|null = null; //This may be null, but should never be undefined (and must be type-guarded before use).var string5: !string|undefined; //this may never be null, but can be undefined (and must be type-guarded before use).

And have a compiler flag (that only works if -noimplicitany is on) which
says -noinferrednulls, which disables the normal syntax for types (like
string, and int) and you have to supply a ? or ! with them (null, undefined
and any being exceptions).

In this manner, non-nullables are an opt-in, and you can use the compiler
flag to force a project to be explicity nulled.
The compiler flag errors at the assignment of types, not after (like
the previous proposal).

Thoughts?

Reply to this email directly or view it on GitHub
#185 (comment)
.

Mike Stay - metaweta@gmail.com
http://www.cs.auckland.ac.nz/~mike
http://reperiendi.wordpress.com

@Griffork that would be an option, but a bad one in my opinion, and I'll explain why :

  • It will cause a lot more works in definition files, since now we will have to check and annotate every type to have the right definition.
  • We will ends up writing !string (and sometimes ?string) everywhere on the code which would make the code a lot less readable.
  • !string in a system where type are nullable is a strange concept the only way you can't really describe it is string minus null minus undefined, on the contrary ?string is pretty simple to describe in a type system where type are null by default string | null | undefined.
  • I foresee a lot of headache ( and perf loss ) to find a type-check algorithm where the compiler understand that string | null require a type guard but string doesn't, you basically introduce a concept where some union type should be treated differently than other.
  • And finally the worst part we completely loose type inference var myString = "hello" what myString should be string, ?string or !string ? honestly a big headache in perspective here.

If we don't have non null type as default, the best proposal I have seen here is the 'use non-null' directive proposed by @metaweta.
Sure it needs to be specified nicely but at least with just a use non-null string in all our file we can get a simple an predictable behavior.

@fdecampredon

  1. It may be a lot more work in definition files, but you'd have to do that work anyway (to ensure the types are correct) and this time the compiler would remind you of what you haven't edited yet (if using -noimplicitnull).
  2. I'm open to other suggestions for annotation. I honestly believe that the current type system has it's place, and should not be replaced. I don't think a breaking change is worth it. Instead I think we should find a better way of describing what you're after. (I really, really dislike the idea of representing any of this with symbols, they're not intuitive.)
  3. What's hard to describe about it? I've seen discussions elsewhere in typescript where this request (for certain types) has been proposed (without a marker). Did a quick search and I couldn't find the issue, I'll hunt more later.
  4. If you're referring to what I had written as !string|null, that would work in the current system if null was treated like {} (but was not assignable to it).
    If you're talking about string|null which I didn't have in my list, then I think null should be ignored in this case. Null and undefined only make sense in unions where every non-null and non-undefined is preceeded with a ! and any isn't an any (this could be a compiler warning/error).
  5. Good question, and one that only arises if you're using the -noimplicitnull option, I think the safest option would be to assign it to which ever option is the most likely to cause an early error (probably nullable), but I get the feeling there's a better idea that I'm not thinking of. I wonder if someone else has a suggestion on how this should be approached?

Edit: Added to point 2.
Edit: Fixed typo in point 4.

It may be a lot more work in definition files, but you'd have to do that work anyway (to ensure the types are correct) and this time the compiler would remind you of what you haven't edited yet (if using -noimplicitnull).

Yes and no, check the library that you use and see how much actually returns null or undefined.
it's a pretty rare case, we could only find in this issue very few occurrence for the standard lib and for example Promise library never do it.
My point is that in a type system where type are not nullable by default most of the existing definition files are already valid.

I'm open to other suggestions for annotation. I honestly believe that the current type system has it's place, and should not be replaced. I don't think a breaking change is worth it. Instead I think we should find a better way of describing what you're after. (I really, really dislike the idea of representing any of this with symbols, they're not intuitive.)

I don't think there is such a way but I hope I am wrong, because it has an inestimable value in my opinion.
For the breaking change part why are you so against the 'use non-null' directive ?
Developers who wish a nullable type system would not be impacted at all (unless they already strangely add 'use non-null' at the top of their file but honestly that would be a bit ... weird)
And developers who wish a non-null type system could just use the new directive.

What's hard to describe about it? I've seen discussions elsewhere in typescript where this request (for certain types) has been proposed (without a marker). Did a quick search and I couldn't find the issue, I'll hunt more later.
I just find the concept a bit 'weird' and not very clear, I generally use composition as the main tool for programming, not decomposition but why not.

If you're referring to what I had written as !string|null, that would work in the current system if null was treated like {} (but was not assignable to it). If you're talking about string|null which I didn't have in my list, then I think null should be ignored in this case. Null and undefined only make sense in unions where every non-null and non-undefined is preceeded with a ! and any isn't an any (this could be a compiler warning/error).
I'm referring to the fact that when you decompose the 3 types :

  • !sting : string - null - undefined
  • string: string | null | undefined
  • ?string: string | null | undefined

The 2 latest have basically no difference, but the compiler should know that for string it should not force type-guard check and for ?string it should, that info will have to be propagate everywhere, the algorithm will be a lot more complex that it is actually, and I'm pretty sure I could find strange case with type inference.

Good question, and one that only arises if you're using the -noimplicitnull option, I think the safest option would be to assign it to which ever option is the most likely to cause an early error (probably nullable), but I get the feeling there's a better idea that I'm not thinking of. I wonder if someone else has a suggestion on how this should be approached?

Won't do it would basically introduce the same problem than @RyanCavanaugh commented about when I thought about just introducing a flag that would allow to switch from null as default to null as default.
In this case the former proposition was a lot more simpler.

Again why are you against the 'use non-null' directive, more I think about it more it seems to me the ideal solution.

@fdecampredon
Because the "use non-null" as has been currently proposed changes the way the language is used not the way the language is written. Meaning that a function from one location when moved to another location may work differently when it's used. You will get compile-time errors that are potentially 1-3 files away because something is typed incorrectly.

The difference between:
string
and
?string
Is that the ? and ! symbol is asking for strict type checking for this variable (much like your "use nonnull" directive would be, but on a per-variable basis). Explain it that way and I think you won't have much trouble with people understanding it.

The difference is of course:
File 1:

//...187 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null;
    }
    else {
        return "hello";
    }
}

File 2:

"use nonnull"
//...2,748 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null; //Error!
    }
    else {
        return "hello";
    }
}

Developers now have to keep a mental map of which files are nonnull and which files aren't. I honestly believe that this is a bad idea (even if most of your code is 'use nonnull').

Edit: Also when you start typing a function and you get the little window that tells you what the function definition is, how do you know if string in that definition is nullable or nonnullable?

@Griffork

  • again that is already the case with 'use strict'.
  • At least the idea is simple. and introduce simple concept in the type system.
  • If a developer move a function in an other file seems a pretty edge case for me, and since the error he will be prompted will be about null check he will quickly be able to understand what's happen ...

Again It's not perfect, but I don't see a better alternative.