TypeScript and Set Theory
ivov.dev
ivov.dev
Here's a type signature from hell with its usage:
type Component = { type: 'a' } | { type: 'b' };
type FoundComponents<T extends any[]> = T extends [a: infer A] ? [Extract<Component, {type: A}>] :
T extends [a: infer A, ...b: infer B ] ? [Extract<Component, {type: A}>, ...FoundComponents<B>] : never;
export function findComponentSets<T extends Component['type'][]>(components: Component[], ...componentTypes: T ): FoundComponents<T>[];
export function findComponentSets(components: Component[], ...componentTypes: Component['type'][] ): Component[][] { return [] }
It recursively picks outputs types based on the input parameters, so if you call e.g. findComponentSets([], 'a', 'b') the output type will be [{type: 'a'}, {type: 'b'}][].I'm using it in an Entity Component System based game I'm writing to type check the function which receives a flat list of Components and type selectors and outputs a list of tuples with the selected Component types.
findComponentSets(components, 'position', 'hit-box')
.forEach(([position, hitBox]) => system.process(position, hitBox));i.e. `ICat | IDog` will only allow `pet.eat()`, and `ICat & IDog` will allow `pet.eat()`, `pet.meow()`, and `pet.bark()`.
Good article otherwise, though. The explanation of top/bottom types was more intuitive than most attempts.
But ultimately more useful? As in, code should never test whether this thing is an ICat or IDog, and I guess it's interesting that I don't remember ever encountering an issue where I attempted to access a member that was only present on one interface.
Perhaps it would be too overloaded, but "ICat + IDog" instead of "ICat & IDog" feels more obvious, I suppose a better convention for "ICat | IDog" is difficult though. "ICat /\ IDog" for intersection and "ICat \/ IDog" for union doesn't roll off the tongue.
Sum<X, Y> = { tag: "left", data: X } | { tag: "right", data: Y }
Generally higher order types are named according to what they do to types, not terms. `x: ICat & IDog` means "x is in the intersection of ICat and IDog", not "x is the intersection of an ICat and an IDog". (The latter interpretation only even makes sense for record types and a few other special cases: what's the intersection of an integer and a string supposed to be?)https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
https://www.typescriptlang.org/docs/handbook/unions-and-inte...
See last paragram under «Unions with common fields»
Leading to terribly confusing TypeScript cheat sheets such as this (see the union and intersection venn-diagrams):
https://carltheperson.com/images/magic-typescript/magic-type...
Compare with: https://en.wikipedia.org/wiki/Intersection_(set_theory)
Luckily TypeScript recently updated their docs, and the issue has been clarified in posts such as this:
https://stackoverflow.com/questions/38855908/naming-of-types...
Turns out, naming things is really hard, because what you implicitly refer to when you unionize or intersect turns out to be of vital importance.
I've created a proposal for doing something similar with the number type that uses the relational operators (<, >, >=, <=) to narrow the types [1].
It may be not as useful as template literal types (which is why one could argue to not implement them), but it's interesting nonetheless.
Ah, this helped my brain click with a problem I’ve encountered several times. I think the most common reason I encounter this is when using generic memoized components in React. The generic parameters seem to be missed by the compiler completely, so something like a memoized polymorphic component becomes impossible to compose further into other components.
I guess the problem is that the memo function has inferred generic parameters which causes distributivity to be disabled on the nested generic parameters.
At least I think this is the case. I need to dig a little deeper, but I’m fairly sure this explains it.
This was a great read. I intuitively think in sets quite often, but it hadn’t occurred to me how overt the set theory is in TypeScript. This opened my eyes to the reason behind certain unintuitive behaviours (especially intersecting interfaces).
const Comp = React.memo(NonMemoComp) as typeof NonMemoCompI wonder if its a Framework like hugo or is it custom built?