Open Liam-Tait opened 1 year ago
Hey! I think I'd use .exhaustive()
and wrap the expression into a try .. catch
to get the behavior you expect. Would that solve your problem?
Im kind of confused about this use-case. In what context would you have a scenario that matches your example of
const unit: 'days' | 'hours' = 'something'
?
Wrapping the expressions in try .. catch
would work, but makes the code verbose and a bit more complicated because of scoping
const value = match(unit)
.with('days', () => 'd')
.with('hours', () => 'h')
.exhaustiveOtherwise(() => 'never')
vs
let value: string
try {
value = match(unit)
.with("days", () => "d")
.with("hours", () => "h")
.exhaustive()
} catch (error) {
value = "never"
}
What I am working with is not an ideal case, the application has partially transitioned to TypeScript. Some types are only partially correct right now.
Adding a catch or otherwise that works with exhaustive means I can start taking advantage of exhaustiveness checking, while preventing errors at runtime while types cannot be fully trusted. Maybe the ideal approach is to have everything be unknown
and handle it that way.
I don't know if this is possible, but having a P.unknown
matcher that only matched unknown
would also work, as then I could add this to types that are not complete yet, this would more accurately show and handle my current situation
const unit: 'days' | 'hours' | unknown;
const value = match(unit)
.with("days", () => "d")
.with("hours", () => "h")
.with(P.unknown, () => 'never')
.exhaustive()
I want to be able to:
unknown
exhaustive
once confidence is higher in the types (validation, types are correct)One of the situations I have is where data is persisted in local storage and loaded back in without any validation, the types "should" be the same, but can be different based on an old stored version, renaming, user changing etc
How about just adding an optional argument to .exhaustive()
that works as same as .otherwise()
? So just like:
declare const input: 'a' | 'b';
const mode = match(input)
.with('a', () => Mode.A)
.with('b', () => Mode.B)
.exhaustive(() => Mode.Fallback)
In addition to that, it'd be better to have the value passed to the callback as unknown
, (since it's impossible to infer valid type in this case) so users can use their own errors than the provided one:
declare const input: 'a' | 'b';
const mode = match(input)
.with('a', () => Mode.A)
.with('b', () => Mode.B)
.exhaustive(v => {
throw new ValidationError(`Unknown mode identifier: ${v}`)
})
I dunno it looks to me like there's a fundamental disagreement about what "exhaustive" means in this context.
In my opinion "exhaustive" loses it's meaning if it can be used in a scenario where you need a fall back
Also, as I understand it there is a performance cost to using exhaustive, so I think it's purpose is pretty narrowly defined.
If you're getting input that you can't type narrow into exhaustive checks then I think there's a strong case to be made that it would be inappropriate to use exhaustive from a conceptual as well as a performance standpoint.
@gvergnaud are you open to PRs regarding this? Ref #38
Chaining, .exhaustiveOtherwise
or some other API would be very useful at times.
Like oguimbal said:
I want the types to break. Not my app.
Could use a try catch but I prefer not to. Here is a workaround (kind of)
import { P, match } from 'ts-pattern'
type Status = 'active' | 'inactive' | 'pending'
const status = 'active' as Status
match(status)
.with('active', () => '...')
.with('inactive', () => '...')
// .with('pending', () => '...')
// ^ uncomment resolve NonExhaustiveError
.with(P.not({}), () => 'fallback')
.exhaustive()
// ~~~~~~~~~~ This expression is not callable.
// Type 'NonExhaustiveError<"pending">' has no call signatures.
Taking inspiration from Effect's orElseAbsurd
, I propose this solution:
declare const unit: string
match(unit)
.with('days', () => 'd')
.with('hours', () => 'h')
.otherwiseAbsurd() // Equivalent to `.otherwise(() => { throw new Error('Absurd!') })`
I think it's much easier to grasp than the other options presented in this issue.
I vote for @XiNiHa's solution:
How about just adding an optional argument to .exhaustive() that works as same as .otherwise()?
I just made the same suggestion over here
Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
We have types, however they are not completely safe. I'd like to be able to ensure the exhaustive type check is run for case such as union where we want to handle where , but they also run an otherwise to prevent throwing of an error.
This means we can have the type checking where a type is partially complete.
Describe the solution you'd like A clear and concise description of what you want to happen.
Add a new method
exhaustiveOtherwise
which does the exhaustive type check with a otherwisehandler
Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.
Allow
otherwise
to chain onto the end ofexhaustive
Add validation such as zod to the data to handle incorrect data (hard as I would like to transition new code to be type safe without having to re-write all types)
Additional context Add any other context or screenshots about the feature request here.