Skip to content

anthonyjoeseph/spectacles-ts

Repository files navigation

spectacles-ts 👓

Practical Optics • Unfancy monocle-ts 🧐

A facade on top of monocle-ts

Blog post

Try it out!

prop video

Installation

yarn add fp-ts spectacles-ts

Examples

get

monocle-ts equivalent

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>

set

monocle-ts equivalent

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[] }

setOption

monocle-ts equivalent

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[] }>

upsert

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 } }

remove

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 } }

rename

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 } }

modify

monocle-ts equivalent

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 }[] }

modifyOption

monocle-ts equivalent

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 }[] }>

modifyW

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 }

modifyOptionW

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 }>

modifyF

monocle-ts equivalent

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 } }>

Operations

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 traverse
Array
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 }
}

Restrictions

For the time being, "strictNullChecks" must be set to false for spectacles to behave properly (see this issue)

Social Media

Follow me on twitter! @typesafeFE