What is optics-ts?
The optics-ts package provides a powerful and type-safe way to work with immutable data structures in TypeScript. It allows you to create and compose lenses, prisms, and other optics to focus on specific parts of your data structures, making it easier to read, update, and transform data in a functional programming style.
What are optics-ts's main functionalities?
Lenses
Lenses allow you to focus on a specific part of a data structure. In this example, we create a lens to focus on the city within the user's address and demonstrate how to get and set the city value.
const { lens } = require('optics-ts');
const user = { name: 'Alice', address: { city: 'Wonderland' } };
const addressLens = lens(user => user.address, (user, address) => ({ ...user, address }));
const cityLens = addressLens.compose(lens(address => address.city, (address, city) => ({ ...address, city })));
const city = cityLens.get(user); // 'Wonderland'
const updatedUser = cityLens.set(user, 'New Wonderland'); // { name: 'Alice', address: { city: 'New Wonderland' } }
Prisms
Prisms are used to focus on a part of a data structure that may or may not be present. In this example, we create a prism to focus on the value inside an option type and demonstrate how to get and set the value.
const { prism } = require('optics-ts');
const some = value => ({ type: 'Some', value });
const none = { type: 'None' };
const optionPrism = prism(
option => (option.type === 'Some' ? option.value : undefined),
value => some(value)
);
const option = some(42);
const value = optionPrism.getOption(option); // 42
const updatedOption = optionPrism.set(option, 100); // { type: 'Some', value: 100 }
Traversal
Traversal allows you to focus on multiple parts of a data structure. In this example, we create a traversal to focus on even numbers in an array and demonstrate how to get all even numbers and modify them.
const { traversal } = require('optics-ts');
const numbers = [1, 2, 3, 4, 5];
const evenTraversal = traversal(
numbers => numbers.filter(n => n % 2 === 0),
(numbers, evens) => numbers.map(n => (n % 2 === 0 ? evens.shift() : n))
);
const evens = evenTraversal.getAll(numbers); // [2, 4]
const updatedNumbers = evenTraversal.modify(numbers, n => n * 2); // [1, 4, 3, 8, 5]
Other packages similar to optics-ts
monocle-ts
monocle-ts is a library for functional optics in TypeScript. It provides similar functionality to optics-ts, including lenses, prisms, and traversals. monocle-ts is well-integrated with the fp-ts library, making it a good choice for projects that already use fp-ts.
ramda
Ramda is a functional programming library for JavaScript that includes support for lenses. While it is not TypeScript-specific, it provides a wide range of functional utilities, including lenses, for working with immutable data structures.
partial.lenses
partial.lenses is a library for working with immutable data structures using lenses in JavaScript. It is highly optimized and provides a rich set of features for creating and composing lenses. It is not TypeScript-specific but can be used with TypeScript.
optics-ts
optics-ts
provides type-safe, ergonomic, polymorphic optics for TypeScript:
- Optics allow you to read or modify values from deeply nested data
structures, while keeping all data immutable.
- Ergonomic: Optics are composed with method chaining, making it easy and
fun!
- Polymorphic: When writing through the optics, you can change the data
types in the nested structure.
- Type-safe: The compiler will type check all operations you do. No
any
,
ever.
➡ Documentation ⬅
Features
optics-ts
supports lenses, prisms, traversals, removing items from containers,
and much more!
Since optics-ts v2.2.0, there are two syntaxes for defining optics: method
chaining (the default) and standalone optics (experimental). See
the docs for more info!
Getting started
Installation:
npm install optics-ts
or
yarn add optics-ts
Here's a simple example demonstrating how lenses can be used to drill into a
nested data structure:
import * as O from 'optics-ts'
type Book = {
title: string
isbn: string
author: {
name: string
}
}
const optic = O.optic_<Book>().prop('author').prop('name')
const input: Book = {
title: "The Hitchhiker's Guide to the Galaxy",
isbn: '978-0345391803',
author: {
name: 'Douglas Adams',
},
}
O.get(optic)(input)
O.set(optic)('Arthur Dent')(input)
O.modify(optic)((str) => str.length + 29)(input)
Another example that converts all words longer than 5 characters to upper case:
import * as O from 'optics-ts/standalone'
const optic = O.optic<string>().words().when(s => s.length >= 5)
const input = 'This is a string with some shorter and some longer words'
O.modify(optic)((s) => s.toUpperCase()(input)
See the documentation for a tutorial and
a detailed reference of all supported optics.
Development
Run yarn
to install dependencies.
Running the test suite
Run yarn test
.
For compiling and running the tests when files change, run these commands in
separate terminals:
yarn build:test --watch
yarn jest dist-test/ --watchAll
Documentation
You need Python 3 to build the docs.
python3 -m venv venv
./venv/bin/pip install mkdocs-material
Run a live reloading server for the documentation:
./venv/bin/mkdocs serve
Open http://localhost:8000/ in the browser.
Releasing
$ yarn version --new-version <major|minor|patch>
$ yarn publish
$ git push origin main --tags
Open https://github.com/akheron/optics-ts/releases, edit the draft release,
select the newest version tag, adjust the description as needed.