A comprehensive approach to comparing subsets within a set

I'm curious about implementing an exhaustive switch/case using the "never" type.

Imagine I have a Set of strings: {A, B} (these strings can be long and the Set can be large). For each subset (like {}, {A,B}), I want to create a function called show: Set => string

Here's some pseudo-code:

function show(subset: Set<string>): string {
    switch(subset) {
        case Set([]): return "Empty";
        case Set([A]): return "This is A";
        case Set([B]): return "This is B";
        case Set([A, B]): return "This is A and B";
        default: assertUnreachable(subset)
    }
}

function assertUnreachable(x: never): never {
    throw new Error("Didn't expect to get here");
}

Is there a way to ensure at compile time that all possible subsets are covered in the show function? For example, if I were to add C to the Set {A, B, C}, would it require me to update the show function with cases for {C}, {A, C}, {B, C}, and {A, B, C}?

Answer â„–1

Now, let's delve into the assumptions and definitions surrounding the T in our Set<T> type, which will always consist of a combination of string literal types. Assuming you will utilize the --strict compiler option, we proceed to define the variables and types as follows:

const A = "A"; type A = typeof A;
const B = "B"; type B = typeof B;

This particular question holds great interest for me due to its complexity. Firstly, it is important to note that Sets cannot be directly compared for equality. Therefore, traditional methods like switch/case are not applicable here. Instead, I present to you a type guard function that enables checking if a specific type of Set<T> is indeed a subset of another type Set<U> where U extends T.

function isSubset<T extends string, U extends T[]>(
  set: Set<T>, 
  ...subsetArgs: U
): set is Set<U[number]> {
  const otherSet = new Set(subsetArgs);
  if (otherSet.size !== set.size) return false;
  for (let v of otherSet.values()) {
    if (!set.has(v)) return false;
  }
  return true;
}

You have the flexibility to customize this function to suit your needs. Essentially, it verifies that an element exists in set only if it also exists in subsetArgs. Here is an illustration of how to implement it:

declare const set: Set<A | B>; // a set containing elements 'A' or 'B'
if isSubset(set) { // no additional arguments, checking for emptiness
   set; // narrowed down to Set<never>
} else if isSubset(set, A) {  // checking for presence of 'A' only
   set; // narrowed down to Set<A>
} 

Observe how the type guard refines the type of set with each condition? Instead of using switch/case, we employ if/else constructs.


The subsequent challenge revolves around the automatic expansion of the type Set<A | B> to encompass all conceivable subset types such as Set<never>, Set<A>, Set<B>, and Set<A | B>. This manual expansion can be achieved through the following technique:

// notice the expanded union of types represented by 'subset'
function show(subset: Set<never> | Set<A> | Set<B> | Set<A | B>): string {
  if (isSubset(subset)) {
    return "Empty";
  } else if (isSubset(subset, A)) {
    return "This is A";
  } else if (isSubset(subset, B)) {
    return "This is B";
  } else if (isSubset(subset, A, B)) {        
    return "Something in German I guess";
  } else {
    return assertUnreachable(subset); // prevents errors
  }
}

console.log(show(new Set([A]))); // output: "This is A"
console.log(show(new Set([A, B]))); // output: "Something in German I guess"
console.log(show(new Set([A, B, "C"])); // compile time error, unexpected "C"
console.log(show(new Set())); // output: "Empty"

This configuration compiles successfully. However, one potential pitfall arises when the TypeScript compiler views Set<A> as a subtype of Set<A | B>. The order of type guard clauses becomes critical—ensuring proper narrowing from narrower to broader types to avoid erroneous compilation conclusions.

function badShow(subset: PowerSetUnion<A | B>): string {
  if (isSubset(subset, A, B)) {
    return "Something in German I guess";
  } else {
    return assertUnreachable(subset); // now triggers an error as expected!
  }
}

Careful sequencing of checks mitigates the aforementioned issue. Alternatively, a more intricate approach involves encapsulating Set<T> within InvariantSet<T> objects to circumvent subtyping relationships:

type InvariantSet<T> = {
  set: Set<T>;
  "**forceInvariant**": (t: T) => T;
}

By enveloping all instances of Set<T> inside InvariantSet<T>, the problem associated with subtype relations between sets is resolved. Nonetheless, this solution introduces increased complexity.


Furthermore, there may arise the necessity to automatically generate all possible subset types from a union type like A | B, resulting in definitions such as

Set<never> | Set<A> | Set<B> | Set<A | B>
. This concept aligns with the notion of a power set.

While achieving an exact power set definition is challenging due to circular dependencies, a workaround can be implemented through extensive yet similar individual definitions limited by a predefined recursion depth. This method accounts for unions up to a specified number of constituents, preventing overwhelming scenarios where managing unions becomes unwieldy. Here is a practical implementation:

type PowerSetUnion<U, V = U> = Set<U> | (V extends any ? (PSU0<Exclude<U, V>>) : never);
// Additional PSUs follow...
type PSUX<U> = Set<U>; // terminating point

Utilizing these distributive conditional types yields the desired outcome for various use cases:

type PowerSetAB = PowerSetUnion<A | B>; // returns the complete power set of 'A | B'

type PowerSetOhBoy = PowerSetUnion<1 | 2 | 3 | 4 | 5 | 6>; // exemplifies power set generation for multiple elements

Feel free to modify the show() signature to accommodate PowerSetUnion<T> for enhanced functionality.


Phew! That was quite a journey, but I trust it provides clarity and guidance for your project endeavors. Best of luck!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to extend the Object class in order to automatically initialize a property when it is being modified?

My experience with various programming languages leads me to believe that the answer is likely a resounding no, except for PHP which had some peculiar cases like $someArray['nonexistentKey']++. I'm interested in creating a sparse object whe ...

Extracting and retrieving the value from the paramMap in Angular/JavaScript

How can we extract only the value from the router param map? Currently, the output is: authkey:af408c30-d212-4efe-933d-54606709fa32 I am interested in obtaining just the random "af408c30-d212-4efe-933d-54606709fa32" without the key "authke ...

Using *ngFor to iterate through elements with distinct styles

Is there a way to assign different classes to buttons generated using *ngFor without changing the class for all previously created buttons when toggled? I have 2 search bars adding text to the same array, displayed as buttons. I want to differentiate betwe ...

Why is webpack attempting to package up my testing files?

In my project, I have two main directories: "src" and "specs". The webpack configuration entrypoint is set to a file within the src directory. Additionally, the context of the webpack config is also set to the src directory. There is a postinstall hook in ...

IntelliJ IDEA does not support the recognition of HTML tags and directives

I seem to have lost the ability to switch between my HTML and TS files in Intellij IDEA; the tags, directives, and autocompletion in HTML are no longer working. Additionally, I'm receiving some warnings: https://i.stack.imgur.com/QjmNk.png Is there ...

The letter 'X' is not suitable for use as a JSX component because its return type 'Element[]' does not qualify as a valid JSX element

Currently, I am working on rendering two simple joke cards in TypeScript. The cards are displaying correctly in my browser, but I've encountered an error message that says: 'Jokes' cannot be used as a JSX component. Its return type 'Ele ...

Best practices for annotating component props that can receive either a Component or a string representing an HTML tag

What is the correct way to annotate component props that can accept either a Component or a string representing an HTML tag? For instance, imagine I have a component that can receive a custom Component (which includes HTML tags like div, p, etc.). The cod ...

``Are you experiencing trouble with form fields not being marked as dirty when submitting? This issue can be solved with React-H

Hey there, team! Our usual practice is to validate the input when a user touches it and display an error message. However, when the user clicks submit, all fields should be marked as dirty and any error messages should be visible. Unfortunately, this isn&a ...

Configuring Monaco Editor in React to be in readonly mode

Here is a code snippet to consider: import React from "react"; import Editor from "@monaco-editor/react"; function App() { return ( <Editor height="90vh" defaultLanguage="javascript" defa ...

react Concealing the Card upon clicking a different location

Utilizing a combination of React and TypeScript, this component allows for the card to be toggled between shown and hidden states upon clicking on the specified div tag. However, there is a need to ensure that the card is hidden even if another area outs ...

Looking for a TypeScript annotation that allows accessing an object property using a variable

When working with plain JavaScript, we have the ability to access an object's property value using a variable. For example, this is permitted: let obj = { a: 100, b: 'Need help with TypeScript', c: new Date() }; let prop = 'b'; c ...

Encountering problem when attempting to incorporate fade in/fade out effect animation

I have a webpage that features multiple buttons, each triggering a fade-in/fade-out animation when clicked. This animation also involves changing the contents of a specific div. Though everything is functioning properly, there is a brief moment (about hal ...

Setting up only two input fields side by side in an Angular Form

I'm fairly new to Angular development and currently working on creating a registration form. I need the form to have two columns in a row, with fields like Firstname and Lastname in one row, followed by Phone, Email, Password, and Confirm Password in ...

Assign a unique identifier to the Angular mat-checkbox component

I need to attach an ID to an Angular material checkbox. I have an object called item. When I check the HTML code, it shows 'item.id + '-input'. However, when I bind the name, it works fine with just 'item.id'. Below is my current ...

Is there a way to specifically target the MUI paper component within the select style without relying on the SX props?

I have been experimenting with styling the Select MUI component using the styled function. I am looking to create a reusable style and move away from using sx. Despite trying various methods, I am struggling to identify the correct class in order to direct ...

What is the process for inputting a value within single quotation marks?

I'm working with a code snippet that looks like this: for(var j=0; j < this.arr.length; j++) { arr.push({ id: 'j', label: this.arr[j], display: () => this.arr[j] }) } I am curious about ho ...

Activating Ionic6 Stack Modal through JavaScript or TypeScript

Is it possible to trigger the modal using script code instead of a button? I have searched through various examples in the tutorial, but all of them rely on the modal trigger mechanism. <ion-button id="open-modal" expand="block">O ...

What is the best way to extract all Enum Flags based on a Number in TypeScript?

Given: Enum: {1, 4, 16} Flags: 20 When: I provide the Flags value to a function Then: The output will be an array of flags corresponding to the given Enum: [4, 16] Note: I attempted to manually convert the Enum to an array and treat values as numb ...

How to customize the radio button style in Angular 11 by changing the text color

Hey guys, I'm working with these radio buttons and have a few questions: <form [formGroup]="myForm" class="location_filter"> <label style="font-weight: 500; color: #C0C0C0">Select a button: </label& ...

What is the best way to fetch data before a component is rendered on the screen?

I am facing an issue with fetching data from a local server in Node. When I try to render the component, the array 'users' from the state appears to be empty, resulting in no users being displayed on the screen as intended. What's strange is ...