safety-lens
v1.5.0
Published
Type-safe, functional lens library supporting Immutable.js
Downloads
39
Maintainers
Readme
safety-lens
Type-safe, functional lens library in JavaScript. This is basically a port of some of the concepts from the Haskell lens library. Safety-lens goes best with Flow, and pairs well with Immutable.
A lens is used to focus in on a specific piece of a data structure. Using a lens, that piece can be read or updated. For example:
import { get, set } from 'safety-lens'
import { prop } from 'safety-lens/es2015'
let obj = { foo: 1, bar: 2 }
// Get a value
assert( get(prop('bar'), obj) === 2 )
obj = set(prop('bar'), 3, obj)
// Set a value
assert( get(prop('bar'), obj) === 3 )
The function call prop('bar')
creates a lens that focuses on the bar
property of any object.
get
takes a lens and an object, and returns a reference to the focused piece
of the object.
So get(prop('bar'), obj)
returns 2
.
set
takes a lens, a new value, and an object, and replaces the focused piece
of the object with the new value.
Or to be more precise, set
creates a copy of the object in which the focused
piece has been replaced with the new value.
In the example above, set(prop('bar'), 3, obj)
creates a new object that sets
the bar
property to 3
, and keeps the foo
property set to 1
.
This is obviously more complicated than writing the equivalent expressions
obj.bar
or obj.bar = 3
.
But lenses do come with several advantages:
Lenses perform immutable updates: you get an updated copy of a data structure, while the original is untouched. This means that lenses pair nicely with immutable data libraries like Immutable or Mori.
Lenses can be composed to focus on deeply nested pieces of a data structure. Updating a deeply-nested, immutable data structure by hand is painful - but lenses make it easy.
Lenses provide type safety. Immutable has methods
getIn
,setIn
; and Mori has equivalent functions. These are invaluable for working with deeply nested data structures. But there is no type checker available today that can check the type of a value returned from one of these functions, or that can check that appropriate types are used in updates. Lenses do the same job, but with dependable type-checking using Flow.Lenses provide encapsulation. When writing a library or module, you can export lenses, but keep the details of your data structures hidden.
Documentation
Please refer to these topics for more information:
- Types of Lenses, covers specialized lens types, and explains type parameters
- API Documentation
Install
npm install --save safety-lens
Building from source
Run:
$ npm install
That invokes the prepublish
npm script,
which runs the type checker, transpiles code, and runs tests.
Requires GNU Make.
Examples of usage
Getting and setting nested values
import { get, set } from 'safety-lens'
import { prop } from 'safety-lens/es2015'
let obj = { foo: 1, bar: 2 }
// Get a value
assert( get(prop('bar'), obj) === 2 )
obj = set(prop('bar'), 3, obj)
// Set a value
assert( get(prop('bar'), obj) === 3 )
Transforming nested values with a function
import { over } from 'safety-lens'
import { prop } from 'safety-lens/es2015'
let obj = { foo: 1, bar: 2 }
obj = over(prop('bar'), x => x * 2, obj)
assert( obj.bar === 4 )
Composing lenses
Imagine a program with values of this type:
import { List } from 'immutable'
type Calendar = List<{ date: Date, title: string }>
const events = List.of(
{ date: new Date(), title: 'get coffee' },
{ date: new Date(), title: 'plan day' }
)
To construct a lens that can focus on the title of the first event in a calendar list:
import { compose, lookup } from 'safety-lens'
import { index } from 'safety-lens/immutable'
import { prop } from 'safety-lens/es2015'
const firstEventLens = index(0)
const titleLens = prop('title')
const firstEventTitleLens = compose(firstEventLens, titleLens)
assert( lookup(firstEventTitleLens, events) === 'get coffee' )
A lens might focus on multiple pieces within a data structure simultaneously. To operate on all events in a calendar:
import { over } from 'safety-lens'
import { traverse } from 'safety-lens/immutable'
const dateLens = prop('date')
const allDatesLens = compose(traverse, dateLens)
// Moves all events forward by one day.
const rescheduled = over(
allDatesLens,
date => new Date(Number(date) + 24 * 60 * 60 * 1000),
events
)