paarthenon / variant

Variant types in TypeScript
https://paarthenon.github.io/variant
Mozilla Public License 2.0
184 stars 3 forks source link

Generic variant usage with function as payload #13

Open ohana54 opened 3 years ago

ohana54 commented 3 years ago

Hi again :)

I'm trying to create a generic variant:

const [Matcher, __Matcher] = genericVariant(({ T }) => ({
  Specific: payload(T),
  Custom: (payload: (v: typeof T) => boolean) => ({ payload }),
}))
type Matcher<T, TType extends GTypeNames<typeof __Matcher> = undefined> = GVariantOf<typeof __Matcher, TType, { T: T }>

Specific is pretty straight-forward, where Custom accepts a function that needs to use the generic type.

When using Specific in a "wrong" way, I get an expected error:

// error due to `number` payload not matching `string`
const matcher: Matcher<string> = Matcher.Specific(5)

When using Custom in a "wrong" way, I don't get an error:

const matcher: Matcher<string> = Matcher.Custom((v: number) => v === 1)

I'm probably doing something wrong with the variant creation, how can I get the generic type of Custom to work?

Thanks!

paarthenon commented 3 years ago

Hey I haven't looked into this yet but it's more likely it's my fault than yours. Generic variants are... hard. I probably overlooked a case. I'll see what I can do.

paarthenon commented 3 years ago

Hello @ohana54. I've finally found freedom from professional obligations (say that five times fast). I've been taking some creative license and rewriting most of the library for variant 3.0. As part of that, I tackled generics. There's a new interface I hope you'll find satisfactory. I've ported your example to use it.

As a brief reminder, Variant 3.0 changes the purpose of the variant() function to be a catch-all creation tool. The function to create a single case is now called variation(). You can see the full list of changes in Issue #17 .

const Matcher = variant(onTerms(({T}) => ({
    Specific: payload(T),
    Custom: (payload: (v: typeof T) => boolean) => ({payload}),
})))
type Matcher<T, TType extends TypeNames<typeof Matcher> = undefined> = GVariantOf<typeof Matcher, TType, {T: T}>;

Note you no longer need a __Matcher variable.

With these changes (and executing on TS 4.2.4) I receive an error when using Custom in a "wrong" way.

const matcher: Matcher<string> = Matcher.Custom((v: number) => v === 1);

Full error:

Type '{ type: "Custom"; payload: (v: number) => boolean; }' is not assignable to type '{ type: "Specific"; payload: string; } | { type: "Custom"; payload: (v: string) => boolean; }'.
  Type '{ type: "Custom"; payload: (v: number) => boolean; }' is not assignable to type '{ type: "Custom"; payload: (v: string) => boolean; }'.
    Types of property 'payload' are incompatible.
      Type '(v: number) => boolean' is not assignable to type '(v: string) => boolean'.
        Types of parameters 'v' and 'v' are incompatible.
          Type 'string' is not assignable to type 'number'.ts(2322)

I've added more details of the new generics implementation in this comment.

ohana54 commented 3 years ago

Hi, sounds great!! I'll track the progress and wait for an alpha/beta version to try.