Closed rjdestigter closed 3 years ago
underscore matcher should include the adt as its argument. useful when using match
and you're not using a named argument for the adt.
@rjdestigter , many, many apologies... I have not seen this until just now! But this idea is awesome 💯
I may change it have specific functions for partiality:
matchP({
})
matchPI(promise)({
})
just to keep backwards compatibility for people. I also wonder how hard it would be to have the argument be the union of all the types not accounted for, i.e.:
matchI(promise)({
success: s => ...,
_: (rest: idle | workingOnIt | failure) =>
})
cheers 👍
@ryanleecode I'm not sure what you mean here, could you clarify? @rjdestigter 's changes affect both match
and matchI
matchI
allows you to supply the adt instance as the first parameter, whereas match
has it come last (which allows you to use it in a point-free context, which I think is what you're talking about?)
U covered what I meant here.
"I also wonder how hard it would be to have the argument be the union of all the types not accounted for, i.e.:"
@pfgray I added matchP and matchPI. I can't figure out how to pass the union of all unaccounted cases.
I think the only way to identify the union of all the types not accounted for is to curry the solution maybe?:
export function matchPI2<ADT extends { _type: string }>(
v: ADT
): <Z, TPartial extends Partial<MatchObj<ADT, Z>>>(
matchObj: TPartial
) => (onElse: (rest: Exclude<ADT, { _type: keyof TPartial }>) => Z) => Z {
return (matchObj) => (onElse) =>
(matchObj as any)[v._type] != null
? (matchObj as any)[v._type](v)
: onElse(v as any);
}
This would allow adding entries to matchObj though that aren't part of the ADT
Yeah, I'm playing with this now...
Perhaps your second solution is a bit better:
type Foo = ADT<{
foo: { value: string };
bar: { value: string };
baz: { baz: number };
}>;
const result = matchPI2(foo)({
baz: (b) => b.baz,
})((hmm) => hmm.value.length);
since we could add it with zero breaking changes...
Here, result
is inferring as unknown
(TS 3.9.4), does it do the same for you, @rjdestigter ?
~I'm not seeing that. I tried on 3.9 and 3.8 as well with no issue~
Edit: I am seeing it now
Actually, I was only seeing it for tests that were using @ts-expect-error
. Everything seems to working fine here using 3.8
I've actually been playing around with using mapped types to calculate the Z
type, instead of letting TS try to infer it:
export function match<
ADT extends { _type: string },
M extends MatchObj<ADT, any>
>(matchObj: M): (v: ADT) => Returns<ADT, M> {
...
}
type Returns<ADT extends { _type: string }, M extends MatchObj<ADT, any>> = {
[K in keyof M]: ReturnType<M[K]>;
}[keyof M];
This let you return different types, but just sums them together, i.e.:
const result = pipe(
foo,
match({
bar: (b) => b.value.length,
foo: (f) => "asdf",
baz: (b) => b.baz,
})
);
here, result
is inferred as string | number
.
I'm gonna merge this as is, and I will probably roll this up with some other things in a v2, thanks for the great idea & work, @rjdestigter !
I've also added a test file to ensure the type safety persisted.
type ADT
will also remove underscore types to prevent conflicts. I upgraded TypeScript so that I could make use of @ts-expect-error in the test file.