pfgray / ts-adt

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

Provide a Prism? #20

Open anthonyjoeseph opened 3 years ago

anthonyjoeseph commented 3 years ago

Not sure if this is desired or out of scope, but it might be handy!

A Prism is an optic used to select part of a Sum type (source) so it seems like a natural fit in this ADT library.

We would probably want to avoid a dependency on monocle-ts, so we'd copy and paste the Prism type. It seems unorthodox, but it's how Elm implemented prisms with the original RemoteData type

Spec:

import { ADT, prism } from 'ts-adt'
import * as Op from 'monocle-ts/lib/Op'
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'

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

const getId: O.Option<string> = pipe(
  Op.id<Xor<number, { id: string }>>(),
  Op.composePrism(prism('right')),
  Op.prop('value'),
  Op.prop('id'),
  op => op.getOption({_type: 'right', value: { id: '3' } })
)
assert.deepStrictEqual(getId, O.some('3'))

Implementation:

import { pipe, identity } from 'fp-ts/function'
import * as O from 'fp-ts/Option'

interface Prism<S, A> {
  readonly getOption: (s: S) => O.Option<A>
  readonly reverseGet: (a: A) => S
}

const makePrism = <TagName extends string>(
  tagName: TagName
) => <ADT extends { [tag in TagName]: string }, Tag extends ADT[TagName]>(
  tag: Tag
): Prism<ADT, Extract<ADT, { [t in TagName]: Tag }>> => ({
  getOption: O.fromPredicate(
    (s): s is Extract<ADT, { [t in TagName]: Tag }> =>
      s[tagName] === tag
  ),
  reverseGet: identity,
})

const prism = makePrism('_type')
pfgray commented 3 years ago

So, another note is that ts-adt doesn't depend on fp-ts, so copy/pasting the Prism interface wouldn't be enough, since we'd still need access to Option... I'm thinking it might make sense to make a ts-adt-fp-ts package that includes fp-ts, monocle and any other fp-ts ecosystem libs

anthonyjoeseph commented 3 years ago

Oh ok - my mistake. I think it could also be ok to copy/paste the Option interface by the same rationale, esp since that seems unlikely to change, but I understand that it feels gross and might introduce bugs.

Do you know of/have ideas for other stuff that might find a home in a ts-adt-fp-ts package?