-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Proposal: Expose Undefined
type as an occupied bottom type
#1298
Comments
|
As Igorbek said, this is allowed in TypeScript PS: An |
Types in TypeScript are all occupied types, akin to nonempty sets. The smallest nonempty set is the set with one element, which is both initial and terminal in the category of TypeScript types and functions between them. There are two named types that the spec claims have one element, "Undefined" and "Null"; for some reason, both are unmentionable. 3.2.5 says "Null" is not a subtype of "Undefined". Since, according to 3.2.6,
"Undefined" has the only claim in the spec to being the bottom type, though this conflicts with 3.2.5's assertion that there is only one value of type "Null", namely The "Void" type, strangely, has two elements, the value It would be convenient to have the bottom type be mentionable for use in generics. The fact that If there were an unoccupied type (say, I propose that 3.2.6 be altered so that the "Undefined" type can be mentioned in generics to allow e.g. functions that explicitly return |
@basarat: I'm mainly referring to a user-defned Option generic type. The parameter there doesn't seem to work. It potentially fails because typescript doesn't accept void function arguments, perhaps? I'm glad void is indeed seen as the bottom type, but the use case I provide doesn't seem to work with void, at least the 1.1 compiler complained about putting void there. |
There doesn't seem to be any mentionable type one can give to function baz() { return undefined; }
var s:string = baz();
var t:number = baz(); By explicitly returning |
It also should be clearly noted that
If As @metaweta said, |
In 1.4 you'll be able to write |
var x: typeof undefined; // x is any
var y = undefined; // y is any |
An example with generics: interface Maybe<T> {
hasValue: boolean;
value(): T;
}
class Some<T> implements Maybe<T> {
constructor(value: T) { this._value = value; }
private _value: T;
hasValue = true;
value() { return this._value; }
}
class Nothing<T> implements Maybe<T> {
hasValue = false;
value(): T { throw new Error("Attempting to dereference Nothing."); }
}
var None: Nothing<Undefined> = new Nothing<Undefined>();
var x: Maybe<string> = None; // compiles Same with immutable lists: interface List<T> {
x(): T;
xs(): List<T>;
map<U>(fn: (t: T) => U): List<U>;
filter(fn: (t: T)=> boolean): List<T>;
// etc.
}
class Cons<T> implements List<T> {
constructor(x: T, xs: List<T>) { this._x = x; this._xs = xs; }
x(): T { return this._x; }
xs(): List<T> {return this._xs; }
private _x: T;
private _xs: List<T>;
map<U>(fn: (t: T) => U): List<U> { return new Cons(fn(this._x), this._xs.map(fn)); }
filter(fn: (t: T)=> boolean): List<T> {
if (fn(this._x))
return new Cons(this._x, this._xs.filter(fn));
else
return this._xs.filter(fn);
}
// etc.
}
class Empty<T> implements List<T> {
x(): T { throw new Error("Attempting to dereference Empty list."); }
xs(): List<T> {throw new Error("Attempting to dereference Empty list."); }
map<U>(fn: (t: T) => U): List<U> {
return Nil; // compiles
}
filter(fn: (t: T)=> boolean): List<T> {
return Nil; // compiles
}
}
var Nil: Empty<Undefined> = new Nothing<Undefined>();
var l: List<string> = Nil; // compiles |
@Igorbek indeed it is. Example: // both these statements in succession are legal
var any_string: any = "string"; // acts like a supertype for all types
var num: number = any_string; // acts like a bottom type I'm guessing this is mainly here for some pragmatic JavaScript compatibility purpose. Even if // assuming ts.14
type Undefined = typeof undefined;
var undef_string: Undefined = "string"; // MUST NOT COMPILE; a bottom type has only one legal value: undefined
var num: number = undef_string; // acts like a bottom type, compiles normally Current behavior is same us the first block. |
Looks like function f<T>(g: ()=>T): T { return g(); } // function for extract return type
function g() {} // simple void function, inferred as `() => void`
var x = f(g); // `x` is inferred as `void`
console.log(x === undefined); // "true", so `x` is undefined actually but function h() { return undefined; } // it's the same as `g`, but inferred as `() => any`
var y = f(h); // `y` is `any`
console.log(x === undefined); // "true", so `y` is undefined as well as `x` |
Maybe |
It seems, though that I think the existing behavior of interpreting var unit = {};
var num: number = unit; // currently doesn't compile since we are assigning Object to number
// if it succeeds, then now:
num.toString() === "[Object object]"; The argument is summarized as: Object is definitely not the bottom type, |
Note that |
A more obvious argument for |
@Eyas if you define your var None: Nothing<void> = new Nothing<void>();
// error TS2322: Type 'Nothing<void>' is not assignable to type 'Maybe<string>':
var x: Maybe<string> = None; |
@danquirk except I don't expect type errors. I do want a bottom type so that I can create something that does compile. I'm precisely saying, that short of |
Ah, yeah, sorry, skimmed the thread a little too fast. For future reference, relatively concise examples like that make for good content in the original post as motivating examples :) |
I think @metaweta 's proposal seems to be the one I like the most so far. @jbondc: A bottom type is still necessary for some code, especially with generics/templates. A "bottom-ish" type that is a subtype of all object types, like For any language that compiles to or interfaces with JavaScript, it seems a bit misleading to have an object type that does not include null or undefined. Since You might need a runtime Basically, by offering a |
Interesting. |
@RyanCavanaugh, not sure if it was said somewhere, but the canonical bottom doesn't have values, whereas void of TS does: function b() : void { } var a = b(); // a is of void, undefined by fact if void was bottom, then var a = b(); would be a runtime crash |
@Artazor interesting. That would certainly be sound, as a function that always throws could be assigned to any type (just that if you evaluate it, you will fail). That makes for an interesting unoccupied bottom type (which is what It also seems that JavaScript already has an occupied bottom type: It happens to be occupied: the value If could be that a function that always throws is a subtype of even |
Undefined
type as an occupied bottom type
Updating the title of this issue to more accurately reflect the proposal. All of the machinery is in place to this proposal:
We only need to be able to name this type. |
The spec should also clarify whether Undefined is a subtype of Null. |
What is a use case of undefined or null types in the absence of non-nullable types? |
Presence of non nullable types doesn't rule out nullables, it only makes
|
@mhegazy Its very useful in generics, even if the value is never actually used: Using
|
@mhegazy, see also #1298 (comment) |
@Eyas your example of type Some<a> = {
some: a;
}
type None = {
none: void;
}
type Optional<a> = Some<a> | None;
function isSome<a>(value: Optional<a>): value is Some<a> {
return 'some' in value;
}
function isNone(value: None) : value is None {
return 'none' in value && (<None>value).none == null;
}
var none : None = { none: undefined };
var value = Math.rand() > 0.5 ? { some: 'hey' } : none;
if (isSome(value)) {
console.log(value.some);
} else {
console.log('nothing');
} |
so basically you don't need a generic for |
|
Proposal
The TypeScript spec says that
Undefined
is the subtype of all types (see 3.2.6).The keyword
Undefined
orNothing
should be added to the language to refer to the type ofundefined
.typeof(undefined)
currently yieldsany
. This should be corrected to return the undefined type.Detail
See http://en.wikipedia.org/wiki/Bottom_type & http://james-iry.blogspot.com/2009/08/getting-to-bottom-of-nothing-at-all.html
The neat thing about structural typing TypeScript is that you don't need to explicitly think about generic type variance. Instead, when you use two generic implementations of non-identical types, then TypeScript uses the input and return types of the different methods and properties to figure out if these two generic types are subtypes or not.
One missing feature, however, seems to be a bottom Nothing/None/Nil type that is a subtype of all types. This then allows us to create neat "empty" types in generic.
The text was updated successfully, but these errors were encountered: