Repository navigation
Special bottom type #3076
Description
Activity
Artazor commented
on May 7, 2015 ContributorAuthorMore actionsIt's obviously related to the #1613
Also related to removing 'void' from a union type and allowing its use as a type guard #1806
Still find it odd that number|void doesn't collapse to -> number.
Artazor commented
on May 8, 2015 ContributorAuthorMore actionsI think that bottom !== void and we can not collapse number|void to number because it represents the state where value is either number or undefined, meanwhile the number means a guaranted number.
Um?
What do you mean? A number type can have values 'null' or 'undefined', a void type can have values 'null' or 'undefined'. A void type is clearly a subset of the number type, it's redundant in a union.
Artazor commented
on May 8, 2015 ContributorAuthorMore actionsI think that you are wrong, but the explanation is tricky. This is the consequence of the "carefully corrupted" type system by Anders Hejlsberg (@ahejlsberg).
null and undefined values are assignable to the number not because of they are part of the numbers domain. They are assignable because of an explicit ad-hoc rule (their type is
any). The type system of the TypeScript purposely allows such assignments, and it is valid to expect thatfunction(a: number) { return a.toExponential(); }
will fail when it is called with null or undefined.
type number is not a strict guarantee that the value is a number, it is a hint that in normal circumstances it should be a number. If you purposely pass the null value to it, then you probably know what you do. In opposite (number|void) says that it is normal to expect an "undefined" value.
Anders Hejlsberg (@ahejlsberg), am I right?
Artazor commented
on May 8, 2015 ContributorAuthorMore actionsI think that it implicitly correlates with the
typeof nulldrama of the ES6.I'd say that
- null is a special marker for missing value
- undefined is the result of querying of things that never was assigned intentionally
- bottom - is the total absence of the result (exception thrown)
where "throw" statement is actually an expression of type bottom
Since TS has no model for exceptions in the type system, I think this workaround would be nice.
Other than the unions rule, I'm assuming it would behave the same as
void? i.e. if you get a value of typebottom(outside of a union), you cannot call any methods on it...I don't like
voidbeing used for this, as I'd prefer ifvoidgot its 1.4 meaning again (void | T = void)Anatoly Ressin (@Artazor) Not sure if there's a right or wrong about 'void' at the moment, it's just ambiguous:
var v1 = function() {} // infers void var v1Assign = v1(); v1Assign = null; // not really a bottom type v1Assign = undefined; var v2 = function () { return null; // infers any } var v3 = function () { return undefined; // infers any } var v4 = function () { return void 0; // infers any, a bit odd looking } var v4Assign = v4(); v4Assign = null; v4Assign = undefined; var v5 = function () { throw new Error('?') // infers void } var v5 = function () { if (0) { return ""; // infers 'string' } if (0) { return null; } if (0) { return undefined; } throw 0; }We do agree there should be a bottom type, in my world I call it void!null!undefined.
type Bottom = void!null!undefined;Artazor commented
on May 8, 2015 ContributorAuthorMore actionsJon (@jbondc) your type exclusion syntax is very relevant (have you proposed the syntax itself anywehere?)
I'd say that
type Bottom = number!number // or any other type
Nevertheles, both
nullandundefinedhas typeany. This is why they are assignable to any type.
So they are not in the domain of the corresponding types. For them type checking is simply off.var x:string = null // ok var y:string = (() => null)() // ok (any is propogated outside) var z:string = (():void => null)() // error: can't assign void to string
Hard decision to type the
void <expression>asany(instead ofvoid) was made only for accommodating for commonvoid 0usage instead ofundefinedfor the purpose of initial value. (facepalm)UPD
unfortunately, it effectively kills the type checking in the following (otherwise idiomatic) simple lambda expressionx => void f()
for example
promiseOfString.then(x => void f(x))Has type
Promise<any>instead ofPromise<void>what is wrong with defining alwaysThrow as follows:
function alwaysThrow<a>() : a { throw new Error(''); }
it works immediately without requiring the presence of the bottom type which, unfortunately, needs runtime support from javascript to be what it is
Artazor commented
on May 9, 2015 ContributorAuthorMore actionsAleksey-Bykov hm?
I'm talking about purely static type inference.The primary goal for the bottom type is preventing pollution of union types when inferred implicitly.
If I understood your suggestion correctly you are talking about writing
function test(a: boolean) { return a ? 123 : alwaysThrows<number>(); }
This, unfortunately, kills the idea of type inference (or at least seriously affects its reputation).
Artazor commented
on May 9, 2015 ContributorAuthorMore actionsOther than the unions rule, I'm assuming it would behave the same as void? i.e. if you get a value of type bottom (outside of a union), you cannot call any methods on it...
Exactly. But you can pass this type to anywhere, because of in real life this passing will never happen because of the exception already thrown.
as I'd prefer if void got its 1.4 meaning again (void | T = void)
Here I don't understand. I think the rule of (void | T = void) is very dangerous and irrelevant. Can you elaborate why you ever thought that it would be useful?
I think that this discussion lacks people from the actual TS team, Mohamed Hegazy (@mhegazy)?
Artazor commented
on May 9, 2015 ContributorAuthorMore actionsAnyway, now I start to think that bottom type should have no name at all. It should be inferred statically.
And if you want to to constrain your function explicitly that it should throw in every branch of the control flow you should write something likefunction parseAndThrow(errorInfo: IErrorInfo) throw { ... }
This is a limited and the strongest form of the checked exceptions which states that the function always throws. And it can be implemented without checked exceptions machinery at all - it simply needs the bottom type internally.
Artazor commented
on May 10, 2015 ContributorAuthorMore actionsJon (@jbondc) excluding would be nice, also I would highly anticipate the intersection type, for the immediate intersection of interfaces:
var x: IMyInterface & IRunnable
In fact, I would be happy if TypeScript would have the following type structure:
(I've mentioned this already in petkaantonov/bluebird#589)
The Type System
of my dream for TypeScript
τ ::= tcon | α | (τ τ) | (Λα.τ) | (τ → τ) | (τ × τ) | ⊤ | ⊥ | (τ ⋂ τ) | (τ ⋃ τ) | (!τ) | (τ ^ τ)
where
- tcon — type constructor like
Either,Maybe,Promise,Intetc.; - α — formal type;
- (τ τ) — type application e.g.
Maybe Int; - (Λα.τ) — type abstraction (generic type) (suggested Type aliases requiring type parameters #1616);
- (τ → τ) — function type;
- (τ × τ) — tuple type, associative;
- ⊤ — top type (equivalent to
any); - ⊥ — bottom type (suggested here);
- (τ ⋂ τ) — type intersection, associative, commutative;
- (τ ⋃ τ) — type union, associative, commutative;
- (!τ) — type negation (type complement to ⊤);
- (τ ^ τ) — checked exceptions, associative.
This system has usual parentheses elimination conventions and two aliases:
- τ1 + τ2 = τ1 ⋃ τ2
- τ1 - τ2 = τ1 ⋂ !τ2
Checked exceptions (τresult ^ τerror) have two simple properties:
- (τ1 ^ τ2) ^ τ3 = τ1 ^ (τ2 ⋃ τ3) — exception propagation from inner computations;
- τ1 ^ (τ2 ^ τ3) = τ1 ^ (τ2 ⋃ τ3) — exception union.
that makes (τ ^ τ) associative. (there are more rules, of course)
In fact if there will be interest I can write down all the sematic rules with typing context in strict algebraical format (using ASF+SDF or Rascal)- tcon — type constructor like
Anatoly Ressin (@Artazor) Do you have examples of use cases for type negation? Never seen that before, which programming language?
I guess:
type a = string; type b = number; type c = boolean; type d = a | b | c; type lotsOfThings = d!a; // b | c ?It doesn't seem too difficult to implement, you just pluck it out of your set of union types.
Anatoly Ressin (@Artazor) I meant
void | T = voidin terms of supported methods / operations. With a type guardval != null- addedIn DiscussionNot yet reached consensusNot yet reached consensus
on Dec 9, 2015 The
bottomtype described in this thread is very close to describing the correct behavior of thenothingtype which is now surfaced as a result of control flow analysis, IMO.Artazor commented
on May 10, 2016 ContributorAuthorMore actionsWesley Wigham (@weswigham) exactly! The bottom type was proposed to reflect the result of the control flow analysis, and surely could not be implemented without proper CFA, which now is available. Isn't it?
- addedFixedA PR has been merged for this issueA PR has been merged for this issueCommittedThe team has roadmapped this issueThe team has roadmapped this issueand removedIn DiscussionNot yet reached consensusNot yet reached consensus
on May 24, 2016 - locked and limited conversation to collaborators
on Jun 18, 2018
I think that it should be special bottom type with the following properties:
two functional types are not assignable to each other if exactly one of them has return type bottom(?);rationale
Let's consider the function that always throws:
and other function that uses it
We expect that
testshould have typenumberinstead of( number | void )In more sophisticated example that involves Promise type declarations:
We expect that
should have the type of
Promise<string>instead ofPromise<string | void>.See petkaantonov/bluebird#589 (comment)
Possible explicit annotation that function always throws is using undefined as the type