question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Performance and usablity of `.exhaustive`

See original GitHub issue

So I noticed that exhaustive has some pretty punishing compile times for any moderately complicated types it needs to match on. I’ve even found it hitting the “union type that is too complex to represent” limit regularly. I don’t have any benchmarks. but I think you are aware of the problem as you mention it your docs. I’m not sure if exhaustive is actually usable except for the most simple cases.

If I had to guess it is because if you have a type like this:

type A = {type: "a", mode: "b" | "c" | "d"} |  {type: "b", mode: "f" | "g"}

You have to generate a union that looks like this?:

  | {type: "a", mode: "b"} 
  | {type: "a", mode: "c"} 
  | {type: "a", mode: "d"} 
  | {type: "b", mode: "f"} 
  | {type: "b", mode: "g"}   

So for example this fairly simple to understand union will completely break exhaustive:

import { Property } from "csstype";

declare const a:
  | { type: "textWithColor"; color: Property.Color }
  | { type: "textWithColorAndBackground"; color: Property.Color; backgroundColor: Property.Color };

match2(a).exhaustive(); // "union type that is too complex to represent"

playground link - takes serveral minutes on my machine to hit the limit

The reason being is that Property.Color is a string union with hundreds of variants. (this is the same kind of example that eventually lead the typescript team to abandon by default inference of template string literals types in 4.2 - https://github.com/microsoft/TypeScript/issues/42416)

So this makes exhaustive pretty much unusable if you have a type with properties that are unions of any moderate size. Either because you will hit the union limit or because the compile times are too extreme to make it practical to use.

This is all fair, and I don’t think I see a way around the issue and keeping the full pattern matching features of the lib.

That said, in 80-90% of cases all I want to match on is the discriminator of a union in order to narrow the types. For example this works in a simple switch and I still get some kind of exhaustiveness checks:

declare const a:
  | { type: "textWithColor"; color: Property.Color }
  | { type: "textWithColorAndBackground"; color: Property.Color; backgroundColor: Property.Color };
  
  
 const aToDescription (a: typeof a): string  => { 
   switch(a.type) {
    case "textWithColor":
       return a.color
     case "textWithColorAndBackground":
       return `${a.color} with a background of ${a.backgroundColor}`
   }
}

I’m wondering if you can offer something that still allows for some kind of exhaustiveness checks on discriminators in order to narrow types down, but compromises on pattern matching features elsewhere for the sake of compile time performance. In the back of my mind I’m thinking about this lib https://paarthenon.github.io/variant/ (mentioned in my my previous issue) because this library can do exhaustiveness checks because it only concerns itself with the discriminators of unions.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:16 (16 by maintainers)

github_iconTop GitHub Comments

2reactions
m-ruttercommented, Feb 22, 2021

Just gave it a quick whirl. It feels great - performance is significantly better. I threw a few large and complex types at it, and I didn’t run into any issues. The error messages you are able to get on run are wonderful. I think you managed to do it - you have made the union explosion an opt in that a user can mitigate by say nesting a match within a match.

Having a usable exhaustive makes me much more likely to start using this library in lots of places, and get buy in by my team.

1reaction
gvergnaudcommented, Feb 22, 2021

👋 I think I found a pretty good implementation of the DeepExclude<A, B> I described above that doesn’t distribute every unions upfront. It seems to work pretty well and performances seem acceptable. See PR #17

I just released the ts-pattern@2.1.3-next.0 pre-release with the updated types for exhaustive(), do you mind installing it on your project and telling me if it solves the performance problem you were facing or at least if it’s better?

You can also play with it in this sandbox: https://codesandbox.io/s/autumn-https-41um5?file=/src/index.ts

Read more comments on GitHub >

github_iconTop Results From Across the Web

Exhaustive Review or "I Can't Believe It's Not There" ...
In our 2009 “Eyetracking Web Usability” book we described exhaustive review as “unconstructive combing of pages and menus.
Read more >
What is Exhaustive Testing? How is it different from ...
The exhaustive testing comes under quality assurance, this means under no circumstance the application will cease to system crash or any other ...
Read more >
Exhaustive Testing
Exhaustive testing is a test approach in which all possible data combinations are used for testing. Exploratory testing includes implicit data combinations ...
Read more >
Functional vs non-functional Testing
After verifying the functionality, the system then can be tested on non-functional metrics, such as performance and usability.
Read more >
Functional and non-functional testing methods you should ...
Non-functional testing focus on the non-functional aspects of an application, such as performance, reliability, usability, and security.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found