pfgray / ts-adt

Generate Algebraic Data Types and pattern matchers
MIT License
316 stars 13 forks source link

Provide a refinement function? #19

Closed anthonyjoeseph closed 3 years ago

anthonyjoeseph commented 3 years ago

This idea is for something like morphic-ts predicates

Not sure if this is desired or out of scope, but it might be handy! A possible use case could be in conjunction w/ fp-ts-routing - makeRefinement could refine groups of routes

Spec:

type Xor<A, B> = ADT<{
  nothing: {};
  left: { value: A };
  right: { value: A };
}>;

const exists = <A, B>() => refinement<Xor<A, B>>()(['left', 'right'])

declare const myXor: Xor<number, string>;

if (exists()(myXor)) {
  myXor._type // _type: "left" | "right"
  myXor.value // value: string | number
}

Implementation:

const makeRefinement = <TagName extends string>(
  tag: TagName
) => <ADT extends { [t in TagName]: string }>() => <Tag extends ADT[TagName]>(
  tags: readonly Tag[]
) => (
  s: ADT
): s is Extract<ADT, { [t in TagName]: Tag }> =>
  tags.includes(s[tag] as string as Tag)

const refinement = makeRefinement('_type')
pfgray commented 3 years ago

@anthonyjoeseph, I really like this idea, however I'm wondering if we rearrange the type parameters, it might make it even easier to use (i.e. you don't have to supply a type parameter):

const makeRefinement = <TagName extends string>(
  tag: TagName
) => <Tag extends string>(
  tags: readonly Tag[]
) => <ADT extends { [t in TagName]: string }>(
  s: ADT
): s is Extract<ADT, { [t in TagName]: Tag }> =>
  tags.includes(s[tag])

const refinement = makeRefinement('_type')

declare const myXor: Xor<number, string>;

if (refinement(['left', 'right'])(myXor)) { // no type params
  myXor._type // _type: "left" | "right"
  myXor.value // value: string | number
}

what do you think?

anthonyjoeseph commented 3 years ago

I love it! I added another feature that causes a type error if any of the refinement tags are misspelled - it's a bit messy, but (I think) it works

playground

It looks great as it is, though, if you'd rather just keep it simple

(edit: cleaned up to use more explicit non-naked type) (edit 2: here's one with better naming conventions)

anthonyjoeseph commented 3 years ago

Would you mind if I made a PR for this?

pfgray commented 3 years ago

@anthonyjoeseph , No, go right ahead