Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: non-nullable type #185

Closed
fdecampredon opened this issue Jul 22, 2014 · 358 comments
Closed

Suggestion: non-nullable type #185

fdecampredon opened this issue Jul 22, 2014 · 358 comments
Assignees
Labels
Committed The team has roadmapped this issue Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@fdecampredon
Copy link

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
@electricessence
Copy link

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.

@zpdDG4gta8XKpMCd
Copy link

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.

@samwgoldman
Copy link

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

@fdecampredon
Copy link
Author

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.

@fdecampredon
Copy link
Author

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)
}

@robertknight
Copy link

@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?.

@robertknight
Copy link

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.

@fdecampredon
Copy link
Author

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.

@johnnyreilly
Copy link

@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?)

@fdecampredon
Copy link
Author

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

@samwgoldman
Copy link

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.

@fdecampredon
Copy link
Author

@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.

@samwgoldman
Copy link

@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.

@fdecampredon
Copy link
Author

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 ?

@RyanCavanaugh
Copy link
Member

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.

@fdecampredon
Copy link
Author

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

@fletchsod-developer
Copy link

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.

@NoelAbrahams
Copy link

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

@spion
Copy link

spion commented Nov 25, 2014

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?

@fdecampredon
Copy link
Author

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

@misfo
Copy link

misfo commented Dec 10, 2014

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.

@metaweta
Copy link

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.

@fdecampredon
Copy link
Author

@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.

@metaweta
Copy link

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() || [];

@fdecampredon
Copy link
Author

@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 ' ?

@metaweta
Copy link

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

@andy-hanson
Copy link

I attempted to mitigate this problem with a special type Op<A> = A | NullType. It seems to work pretty well. See here.

@mhegazy mhegazy added In Discussion Not yet reached consensus and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Feb 20, 2016
@pietro909
Copy link

+1 for --noImplicitNull as well PLEASE 👍

@RyanCavanaugh RyanCavanaugh added Committed The team has roadmapped this issue and removed In Discussion Not yet reached consensus labels Feb 29, 2016
@rupebac
Copy link

rupebac commented Apr 1, 2016

+1 for --noImplicitNull

@Gaelan
Copy link

Gaelan commented Apr 3, 2016

Should this be closed?

@dead-claudia
Copy link

@Gaelan Given #7140 is merged, if you would like to file a new, dedicated issue for --noImplicitNull as suggested by a few people here, then it's probably safe to do so now.

@Gaelan
Copy link

Gaelan commented Apr 4, 2016

@isiahmeadows It would probably be better to leave this open then.

@mhegazy
Copy link
Contributor

mhegazy commented Apr 5, 2016

Should this be closed?

We think #2388 is the renaming part of this work. This is why we have not declared this feature complete yet.

if you would like to file a new, dedicated issue for --noImplicitNull as suggested by a few people here, then it's probably safe to do so now.

I am not sure i understand what is requested semantics of this new flag. i would recommend opening a new issue with a clear proposal.

@dead-claudia
Copy link

@mhegazy The idea posited earlier in this issue for --noImplicitNull was that everything has to be explicitly ?Type or !Type. IMHO I don't feel it's worth the boilerplate when there's another flag that infers non-nullable by default that IIRC was already implemented when nullable types themselves were.

@ahejlsberg
Copy link
Member

Closing now that #7140 and #8010 are both merged.

@ahejlsberg ahejlsberg added this to the TypeScript 2.0 milestone Apr 26, 2016
@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Apr 26, 2016
@massimiliano-mantione
Copy link

Sorry if I comment on a closed issue but I don't know a better place where to ask and I don't think this is worth a new issue if there's no interest.
Would it be feasible to handle implicit null on a per-file basis?
Like, handle a bunch of td files with noImplicitNull (because they come from definitelytyped and were conceived that way) but handle my source as implicitNull?
Would anybody find this useful?

@mhegazy
Copy link
Contributor

mhegazy commented May 17, 2016

@massimiliano-mantione, please see #8405

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Committed The team has roadmapped this issue Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests