Optic
import { pipe } from '@stopcock/fp'import * as Optic from '@stopcock/fp/optic'The five dual read and update operations appear with both shapes in the complete dual-call catalogue.
The optic module represents immutable reads and updates without hiding partiality. Its optic kinds are:
| Kind | Focus | Read | Write |
|---|---|---|---|
Lens | exactly one | yes | yes |
Optional | zero or one | Option | yes when present |
Prism | one case of a sum | Option | yes when matched |
Traversal | zero or more | collect | modify all |
Iso | exactly one | yes | reversible |
Getter | exactly one | yes | no |
Fold | zero or more | collect | no |
Setter | zero or more | no | modify |
At | keyed optional value | Option | insert/delete |
Constructors
Section titled “Constructors”Optic.lens(get, replace)Optic.optional(preview, replace)Optic.prism(preview, replace)Optic.traversal(collect, modify)Optic.iso(to, from)Optic.getter(get)Optic.fold(collect)Optic.setter(modify)Common focused constructors are provided:
const name = Optic.prop<User, 'name'>('name')const first = Optic.index<User>(0)const admin = Optic.find<User>((user) => user.role === 'admin')const everyUser = Optic.each<User>()const adults = Optic.filtered<User>((user) => user.age >= 18)const preference = Optic.atKey<'theme', Theme>('theme')const byId = Optic.at<string, User>('user-1')Read and update
Section titled “Read and update”Optic.view(name, user)pipe(user, Optic.view(name))Optic.preview(first, users) // Option<User>pipe(users, Optic.preview(first)) // Option<User>Optic.collect(adults, users) // readonly User[]pipe(users, Optic.collect(adults)) // readonly User[]Optic.set(name, user, 'Ada')pipe(user, Optic.set(name, 'Ada'))Optic.modify(name, user, (value) => value.toUpperCase())pipe( user, Optic.modify(name, (value) => value.toUpperCase()),)Each operation accepts its full argument list for a direct call. Omitting the
source curries the optic (and any other argument) into a pipe step:
const updated = pipe( user, Optic.modify(name, (value) => value.toUpperCase()),)Composition
Section titled “Composition”const city = Optic.compose( Optic.prop<User, 'address'>('address'), Optic.prop<Address, 'city'>('city'),)
Optic.view(city, user)pipe(user, Optic.view(city))Composition preserves the strongest valid optic kind: lens with lens remains a lens, while partial or multi-focus paths become optional or traversal optics.
For nested object paths, the fluent builder avoids manual intermediate types:
const city = Optic.optic<User>().prop('address').prop('city').valueOptic.laws exposes the standard lens get-set, set-get, and set-set checks for
custom lenses.