monocle-ts icon indicating copy to clipboard operation
monocle-ts copied to clipboard

Automatic generation of Prisms?

Open benny-medflyt opened this issue 7 years ago • 3 comments

Lens.fromProp is cool, but what about prisms?

This introduction manually constructs a Prism called "office". Can we have a function that does this automatically?

Automatic Prism generation in Haskell: https://hackage.haskell.org/package/lens-4.15.4/docs/Control-Lens-TH.html#v:makePrisms

benny-medflyt avatar Aug 09 '17 20:08 benny-medflyt

Can we have a function that does this automatically?

It depends on the shape of the union I guess (the general case seems too hard).

For example if you restrict the use case to types which have a discriminant and a payload

import { Prism } from '../src'
import { Option, some, none, fromNullable } from 'fp-ts/lib/Option'

interface A {
  type: 'A'
  payload: string
}
interface B {
  type: 'B'
  payload: number
}
interface C {
  type: 'C'
  payload: boolean
}
type U = A | B | C

function fromDiscriminatedUnion<Tag extends string, Payload extends string>(
  tag: Tag,
  payload: Payload
): <S extends { [K in Tag | Payload]: any }, A extends S>(t: A[Tag]) => Prism<S, A[Payload]> {
  return t =>
    new Prism(
      s => (s[tag] === t ? some(s[payload]) : none),
      a => {
        const ret: any = {}
        ret[tag] = t
        ret[payload] = a
        return ret
      }
    )
}

const prism = fromDiscriminatedUnion('type', 'payload')<U, A>('A')

console.log(prism.getOption({ type: 'B', payload: 1 })) // => none
console.log(prism.getOption({ type: 'A', payload: 'foo' })) // => some("foo")
console.log(prism.reverseGet('foo')) // => { type: 'A', payload: 'foo' }

gcanti avatar Aug 10 '17 06:08 gcanti

It's possible to do this in general now using Extract

import { Prism } from 'monocle-ts'
import { some, none } from 'fp-ts/lib/Option'

interface A {
  type: 'A',
  foo: string
}

interface B {
  type: 'B',
  bar: number
}

interface C {
  type: 'C',
  baz: boolean,
}

type Union = A | B | C

const fromDiscriminatedUnion = <U>() =>
  <K extends keyof U, V extends U[K]>(key: K, value: V) =>
    new Prism<U, Extract<U, {[_ in K]: V}>>(
      union => (union[key] === value ? some(union) : none as any),
      s => s
    )

const prism = fromDiscriminatedUnion<Union>()('type', 'B')

DylanRJohnston avatar Apr 04 '19 06:04 DylanRJohnston

You can simplify it by agreeing to some convention in naming the discriminator

const fromDiscriminatedUnion = <U extends {type: string | number | symbol}>() =>
  <Type extends U['type']>(type: Type) =>
    new Prism<U, Extract<U, {type: Type}>>(
      union => (union.type === type ? some(union) : none as any),
      s => s
    )

const foo = fromDiscriminatedUnion<Union>()('B')

DylanRJohnston avatar Apr 04 '19 06:04 DylanRJohnston