Skip to content

Instantly share code, notes, and snippets.

@mvaldesdeleon
Created August 1, 2020 18:32
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvaldesdeleon/f40667901b01f2edc88583c64caa7bb6 to your computer and use it in GitHub Desktop.
Save mvaldesdeleon/f40667901b01f2edc88583c64caa7bb6 to your computer and use it in GitHub Desktop.
Types as Sets, draft
// 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