monocle-ts
monocle-ts copied to clipboard
Automatic generation of Prisms?
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
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' }
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')
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')