Created
August 1, 2020 18:32
-
-
Save mvaldesdeleon/f40667901b01f2edc88583c64caa7bb6 to your computer and use it in GitHub Desktop.
Types as Sets, draft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Types as Sets, containing Values | |
// Union and Intersection Types can be thought as unions and intersections of the underlying Sets | |
// Type Compatibility can be thought of a subset relationship between the underlying Sets | |
// | |
// Usual Set concepts apply, such as: | |
// cardinality (the number of possible values of a given type) | |
// ø (empty set) | |
// unions, intersections, subsets, supersets, sums, products | |
// | |
// Lets look at some types... | |
// | |
let my_bool : boolean; | |
let my_string : string; | |
let my_date : Date; | |
let my_tuple : [boolean, string, number]; | |
let my_union : number | string; | |
let my_nullable: string | null; | |
let my_literal_union: 'foo' | 'bar' | 'baz'; | |
let my_smaller_union: 'foo' | 'bar'; | |
//my_literal_union = my_smaller_union; | |
//my_smaller_union = my_literal_union; | |
let my_intersection : ('foo' | 'bar' | 'baz') & ('bar' | 'baz' | 'qux'); // 'bar' | 'baz' | |
let my_bad_intersection: ('foo' | 'bar') & ('baz' | 'qux'); // never | |
// Some special types: | |
// Any, Unknown | |
// Void, Never | |
// Any is peculiar, as it disables type-checking completely. With the exception of the type Never, values of type Any can be assigned | |
// to variables of all other types, and vice-versa. Thus, it behaves as both a subset and a superset for all non-Never types. | |
// As part of an intersection type it behaves as a subset and absorbs all non-Never types. | |
let any_and_unknown : any & unknown; // any | |
let any_and_undefined : any & undefined; // any | |
let any_and_null : any & null; // any | |
let any_and_void : any & void; // any | |
let any_and_number : any & number; // any | |
// As part of an union type it behaves as a subset and absorbs all non-Never types. | |
let any_or_unknown : any | unknown; // any | |
let any_or_undefined : any | undefined; // any | |
let any_or_null : any | null; // any | |
let any_or_void : any | void; // any | |
let any_or_number : any | number; // any | |
// Never, as we will see shortly, behaves as a proper empty set and thus is a subset of every other set including Any. | |
let any_and_never : any & never; // never | |
let any_or_never : any | never; // any | |
// For the remaining three types, we will set aside their interaction with Any. | |
// Unknown represents a type about which we do not have any further information. Values of type Unknown could potentially be anything, and values of any type can be assigned to it. It behaves as a superset of all non-Any types. | |
// As part of an intersection type, it does not change the result type. | |
let unknown_and_undefined : unknown & undefined; // undefined | |
let unknown_and_null : unknown & null; // null | |
let unknown_and_void : unknown & void; // void | |
let unknown_and_never : unknown & never; // never | |
let unknown_and_number : unknown & number; // number | |
// As part of an union type, it absorbs all non-Any types. | |
let unknown_or_undefined : unknown | undefined; // unknown | |
let unknown_or_null : unknown | null; // unknown | |
let unknown_or_void : unknown | void; // unknown | |
let unknown_or_never : unknown | never; // unknown | |
let unknown_or_number : unknown | number; // unknown | |
// Never represents a type with no values, which like we mentioned, would be the empty set, and makes Never a subset of all types. In intersection and union types, it behaves opposite to Unknown. | |
// As part of an intersection type, it absorbs all non types (even Any!). | |
let never_and_unknown : never & unknown; // never | |
let never_and_undefined : never & undefined; // never | |
let never_and_null : never & null; // never | |
let never_and_void : never & void; // never | |
let never_and_number : never & number; // never | |
// As part of an union type, it does not change the result type. | |
let never_or_unknown : never | unknown; // unknown | |
let never_or_undefined : never | undefined; // undefined | |
let never_or_null : never | null; // null | |
let never_or_void : never | void; // void | |
let never_or_number : never | number; // number | |
// Finally, lets look at Void. Void is meant to be used in the "classical" "C" fashion, for annotating functions that | |
// do not have a return statement. And we know that if we inspect the value returned in these situations, it will be undefined. | |
// Lets see how it behaves as part of an intersection type. | |
// Void is a subset of Unknown | |
let void_and_unknown : void & unknown; // void | |
// Void is a superset of Undefined | |
let void_and_undefined : void & undefined; // undefined | |
// Void has no values in common with Null | |
let void_and_null : void & null; // never | |
// Void is a superset of Never | |
let void_and_never : void & never; // never | |
// Void has no values in common with any other types | |
let void_and_number : void & number; // never | |
// So from this it seems that Void appears to be a type that contains a single value: undefined | |
// Lets see if the behavior as part of a union type confirms this. | |
// Void is a subset of Unknown | |
let void_or_unknown : void | unknown; // unknown | |
// Void is a superset of Undefined | |
let void_or_undefined : void | undefined; // void | |
// Void is a superset of Null (!) | |
let void_or_null : void | null; // void | |
// Void is a superset of Never | |
let void_or_never : void | never; // void | |
// Void has no values in common with any other types | |
let void_or_number : void | number; // void | number | |
// Sadly, we can see that Void does not appear to be well-behaved, as it claims to be both a superset of Null, and have | |
// no values in common with Null at the same time. So we will have to set it aside, along with Any. | |
// But what remains is a well-behaved system of Types as Sets. | |
// Can we define a Type Equality operator based on set equality (A and B are equal if and only if A is a subset of B and | |
// B is a subset of A)? Yes! Almost. Kind-of. Yes! | |
// We need to avoid the Distributive Conditional | |
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types | |
type SameType<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment