Practical Optics • Unfancy monocle-ts 🧐
A facade on top of monocle-ts
yarn add fp-ts spectacles-ts
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import { get } from 'spectacles-ts'
const gotten = pipe(
{ a: { b: ['abc', 'def'] } },
get('a.b.[number]', 0)
)
// gotten: O.Option<string>
immutability-helper equivalent
import { pipe } from 'fp-ts/function'
import { set } from 'spectacles-ts'
const beenSet = pipe(
{ a: ['abc', 'def'] },
set('a.[number]', 1, 'xyz')
)
// beenSet: { a: string[] }
import { pipe } from 'fp-ts/function'
import { setOption } from 'spectacles-ts'
const setOptioned = pipe(
{ a: ['abc', 'def'] },
setOption('a.[number]', 1, 'xyz')
)
// setOptioned: O.Option<{ a: string[] }>
immutability-helper equivalents
import { pipe } from 'fp-ts/function'
import { upsert } from 'spectacles-ts'
const upserted = pipe(
{ a: { b: 123 } },
upsert('a', 'c', 'abc')
)
// upserted: { a: { b: number; readonly c: string } }
import { pipe } from 'fp-ts/function'
import { remove } from 'spectacles-ts'
const removed = pipe(
{ a: { b: 123, c: false } },
remove('a.c')
)
// removed: { a: { b: number } }
import { pipe } from 'fp-ts/function'
import type { NonEmptyArray } from 'fp-ts/NonEmptyArray'
import type { Option } from 'fp-ts/Option'
import { rename } from 'spectacles-ts'
const renamed = pipe(
{ a: { b: 123, c: 'abc' } },
rename('a.c', 'd')
)
// renamed: { a: { b: number; readonly d: string } }
immutability-helper equivalent
import { pipe } from 'fp-ts/function'
import { modify } from 'spectacles-ts'
const modified = pipe(
{ a: [{ b: 123 }] },
modify('a.[number].b', 0, (j) => j + 4)
)
// modified: { a: { b: number }[] }
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import { modifyOption } from 'spectacles-ts'
const modifyOptioned = pipe(
{ a: [{ b: 123 }] },
modifyOption('a.[number].b', 0, (j) => j + 4)
)
// modifyOptioned: O.Option<{ a: { b: number }[] }>
import { pipe } from 'fp-ts/function'
import { modifyW } from 'spectacles-ts'
const modifyWidened = pipe(
{ a: 123 } as { a: number | undefined },
modifyW('a?', (j) => `${j + 2}`)
)
// modifyWidened: { a: string | undefined }
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import { modifyOptionW } from 'spectacles-ts'
const modifyOptionWidened = pipe(
{ a: 123 } as { a: number | undefined },
modifyOptionW('a?', (j) => `${j + 2}`)
)
// modifyOptionWidened: O.Option<{ a: string | undefined }>
import { pipe } from 'fp-ts/function'
import * as E from 'fp-ts/Either'
import { modifyF } from 'spectacles-ts'
const modifieFunctored = pipe(
{ a: { b: 123 } },
modifyF(E.Applicative)(
'a.b',
(j) => j > 10 ? E.left<string, never>('fail') : E.right(j - 10)
)
)
// modifieFunctored: E.Either<string, { a: { b: number } }>
usage | equals | Optional | monocle |
---|---|---|---|
get('a')(x) |
1 |
no | prop |
get('c.[0]')(x) |
123 |
no | component |
get('d.[number]', 0)(x) |
O.some({ e: 123 }) |
yes | index |
get('f.[string]', 'a')(x) |
O.some([123]) |
yes | key |
get('g?')(x) |
O.some(2) |
yes | fromNullable |
get('h.?some')(x) |
O.some(2) |
yes | some |
get('i.?left')(x) |
O.none |
yes | left |
get('i.?right')(x) |
O.some(2) |
yes | right |
get('j.shape:circle.radius')(x) |
O.some(100) |
yes | filter |
get('d.[]>.e')(x) |
[123, 456] |
never | traverseArray |
get('f.{}>.e')(x) |
[123, 456] |
never | traverse Record (keys sorted alpha- betically) |
import * as O from 'fp-ts/Option'
import * as E from 'fp-ts/Either'
interface Data {
a: number
b: number
c: [number, string]
d: { e: number }[]
f: Record<string, number[]>
g?: number
h: O.Option<number>
i: E.Either<string, number>
j: { shape: "circle"; radius: number } | { shape: "rectangle"; width: number; height: number }
}
const x: Data = {
a: 1,
b: 2,
c: [123, 'abc'],
d: [{ e: 123 }, { e: 456 }],
f: { b: { e: 456 }, a: { e: 123 } },
g: 2,
h: O.some(2),
i: E.right(2),
j: { shape: "circle", radius: 100 }
}
For the time being, "strictNullChecks" must be set to false
for spectacles to behave properly (see this issue)
Follow me on twitter! @typesafeFE