Motivation
(Adapted from monocle site)
Modifying immutable nested object in JavaScript is verbose which makes code difficult to understand and reason about.
Let's have a look at some examples:
interface Street { num: number, name: string }
interface Address { city: string, street: Street }
interface Company { name: string, address: Address }
interface Employee { name: string, company: Company }
Let’s say we have an employee and we need to upper case the first character of his company street name. Here is how we could write it in vanilla JavaScript
const employee: Employee = {
name: "john",
company: {
name: "awesome inc",
address: {
city: "london",
street: {
num: 23,
name: "high street"
}
}
}
}
const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1)
const employee2 = {
...employee,
company: {
...employee.company,
address: {
...employee.company.address,
street: {
...employee.company.address.street,
name: capitalize(employee.company.address.street.name)
}
}
}
}
As we can see copy is not convenient to update nested objects because we need to repeat ourselves. Let's see what could we do with monocle-ts
import { Lens, Optional } from 'monocle-ts'
const company = Lens.fromProp<Employee, 'company'>('company')
const address = Lens.fromProp<Company, 'address'>('address')
const street = Lens.fromProp<Address, 'street'>('street')
const name = Lens.fromProp<Street, 'name'>('name')
company.compose(address).compose(street).compose(name)
compose
takes two Lenses
, one from A
to B
and another one from B
to C
and creates a third Lens
from A
to C
.
Therefore, after composing company
, address
, street
and name
, we obtain a Lens
from Employee
to string
(the street name).
Now we can use this Lens
issued from the composition to modify the street name using the function capitalize
company
.compose(address)
.compose(street)
.compose(name)
.modify(capitalize)(employee)
Here modify
lift a function string => string
to a function Employee => Employee
. It works but it would be clearer if we could zoom
into the first character of a string
with a Lens
. However, we cannot write such a Lens
because Lenses
require the field they are directed
at to be mandatory. In our case the first character of a string
is optional as a string
can be empty. So we need another abstraction that
would be a sort of partial Lens, in monocle-ts
it is called an Optional
.
import { some, none } from 'fp-ts/lib/Option'
const firstLetter = new Optional<string, string>(
s => s.length > 0 ? some(s[0]) : none,
a => s => a + s.substring(1)
)
company
.compose(address)
.compose(street)
.compose(name)
.asOptional()
.compose(firstLetter)
.modify(s => s.toUpperCase())(employee)
Similarly to compose
for lenses, compose
for optionals takes two Optionals
, one from A
to B
and another from B
to C
and creates a third Optional
from A
to C
.
All Lenses
can be seen as Optionals
where the optional element to zoom into is always present, hence composing an Optional
and a Lens
always produces an Optional
.
Table of Contents generated with DocToc
Iso
class Iso<S, A> {
constructor(readonly get: (s: S) => A, readonly reverseGet: (a: A) => S)
}
Methods
unwrap
(s: S) => A
Alias of get
to
(s: S) => A
Alias of get
wrap
(a: A) => S
Alias of reverseGet
from
(a: A) => S
Alias of reverseGet
modify
(f: (a: A) => A): (s: S) => S
asLens
(): Lens<S, A>
view an Iso as a Lens
asPrism
(): Prism<S, A>
view an Iso as a Prism
asOptional
(): Optional<S, A>
view an Iso as a Optional
asTraversal
(): Traversal<S, A>
view an Iso as a Traversal
asFold
(): Fold<S, A>
view an Iso as a Fold
asGetter
(): Getter<S, A>
view an Iso as a Getter
asSetter
(): Setter<S, A>
view an Iso as a Setter
compose
<B>(ab: Iso<A, B>): Iso<S, B>
compose an Iso with an Iso
composeLens
<B>(ab: Lens<A, B>): Lens<S, B>
compose an Iso with a Lens
composePrism
<B>(ab: Prism<A, B>): Prism<S, B>
compose an Iso with a Prism
composeOptional
<B>(ab: Optional<A, B>): Optional<S, B>
compose an Iso with an Optional
composeTraversal
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose an Iso with a Traversal
composeFold
<B>(ab: Fold<A, B>): Fold<S, B>
compose an Iso with a Fold
composeGetter
<B>(ab: Getter<A, B>): Getter<S, B>
compose an Iso with a Getter
composeSetter
<B>(ab: Setter<A, B>): Setter<S, B>
compose an Iso with a Setter
Lens
class Lens<S, A> {
constructor(readonly get: (s: S) => A, readonly set: (a: A) => (s: S) => S)
}
fromPath
<T, K1 extends keyof T>(path: [K1]): Lens<T, T[K1]>
Example
type Person = {
name: string
age: number
address: {
city: string
}
}
const city = Lens.fromPath<Person, 'address', 'city'>(['address', 'city'])
const person: Person = { name: 'Giulio', age: 43, address: { city: 'Milan' } }
console.log(city.get(person))
console.log(city.set('London')(person))
fromProp
<T, P extends keyof T>(prop: P): Lens<T, T[P]>
Example
type Person = {
name: string
age: number
}
const age = Lens.fromProp<Person, 'age'>('age')
const person: Person = { name: 'Giulio', age: 43 }
console.log(age.get(person))
console.log(age.set(44)(person))
Methods
modify
(f: (a: A) => A): (s: S) => S
asOptional
(): Optional<S, A>
view a Lens as a Optional
asTraversal
(): Traversal<S, A>
view a Lens as a Traversal
asSetter
(): Setter<S, A>
view a Lens as a Setter
asGetter
(): Getter<S, A>
view a Lens as a Getter
asFold
(): Fold<S, A>
view a Lens as a Fold
compose
<B>(ab: Lens<A, B>): Lens<S, B>
compose a Lens with a Lens
composeGetter
<B>(ab: Getter<A, B>): Getter<S, B>
compose a Lens with a Getter
composeFold
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Lens with a Fold
composeOptional
<B>(ab: Optional<A, B>): Optional<S, B>
compose a Lens with an Optional
composeTraversal
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose a Lens with an Traversal
composeSetter
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Lens with an Setter
composeIso
<B>(ab: Iso<A, B>): Lens<S, B>
compose a Lens with an Iso
composePrism
<B>(ab: Prism<A, B>): Optional<S, B>
compose a Lens with a Prism
Prism
class Prism<S, A> {
constructor(readonly getOption: (s: S) => Option<A>, readonly reverseGet: (a: A) => S)
}
fromPredicate
<A>(predicate: Predicate<A>): Prism<A, A>
some
<A>(): Prism<Option<A>, A>
Methods
modify
(f: (a: A) => A): (s: S) => S
modifyOption
(f: (a: A) => A): (s: S) => Option<S>
set
(a: A): (s: S) => S
set the target of a Prism with a value
asOptional
(): Optional<S, A>
view a Prism as a Optional
asTraversal
(): Traversal<S, A>
view a Prism as a Traversal
asSetter
(): Setter<S, A>
view a Prism as a Setter
asFold
(): Fold<S, A>
view a Prism as a Fold
compose
<B>(ab: Prism<A, B>): Prism<S, B>
compose a Prism with a Prism
composeOptional
<B>(ab: Optional<A, B>): Optional<S, B>
compose a Prism with a Optional
composeTraversal
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose a Prism with a Traversal
composeFold
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Prism with a Fold
composeSetter
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Prism with a Setter
composeIso
<B>(ab: Iso<A, B>): Prism<S, B>
compose a Prism with a Iso
composeLens
<B>(ab: Lens<A, B>): Optional<S, B>
compose a Prism with a Lens
composeGetter
<B>(ab: Getter<A, B>): Fold<S, B>
compose a Prism with a Getter
Optional
class Optional<S, A> {
constructor(readonly getOption: (s: S) => Option<A>, readonly set: (a: A) => (s: S) => S) {}
}
fromNullableProp
<S, A extends S[K], K extends keyof S>(k: K): Optional<S, A>
Example
interface Phone {
number: string
}
interface Employment {
phone?: Phone
}
interface Info {
employment?: Employment
}
interface Response {
info?: Info
}
const info = Optional.fromNullableProp<Response, Info, 'info'>('info')
const employment = Optional.fromNullableProp<Info, Employment, 'employment'>('employment')
const phone = Optional.fromNullableProp<Employment, Phone, 'phone'>('phone')
const number = Lens.fromProp<Phone, 'number'>('number')
const numberFromResponse = info
.compose(employment)
.compose(phone)
.composeLens(number)
const response1: Response = {
info: {
employment: {
phone: {
number: '555-1234'
}
}
}
}
const response2: Response = {
info: {
employment: {}
}
}
numberFromResponse.getOption(response1)
numberFromResponse.getOption(response2)
Methods
modify
(f: (a: A) => A): (s: S) => S
modifyOption
(f: (a: A) => A): (s: S) => Option<S>
asTraversal
(): Traversal<S, A>
view a Optional as a Traversal
asFold
(): Fold<S, A>
view an Optional as a Fold
asSetter
(): Setter<S, A>
view an Optional as a Setter
compose
<B>(ab: Optional<A, B>): Optional<S, B>
compose a Optional with a Optional
composeTraversal
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose an Optional with a Traversal
composeFold
<B>(ab: Fold<A, B>): Fold<S, B>
compose an Optional with a Fold
composeSetter
<B>(ab: Setter<A, B>): Setter<S, B>
compose an Optional with a Setter
composeLens
<B>(ab: Lens<A, B>): Optional<S, B>
compose an Optional with a Lens
composePrism
<B>(ab: Prism<A, B>): Optional<S, B>
compose an Optional with a Prism
composeIso
<B>(ab: Iso<A, B>): Optional<S, B>
compose an Optional with a Iso
composeGetter
<B>(ab: Getter<A, B>): Fold<S, B>
compose an Optional with a Getter
Traversal
class Traversal<S, A> {
constructor(
readonly modifyF: <F>(F: Applicative<F>) => (f: (a: A) => HKT<F, A>) => (s: S) => HKT<F, S>
)
}
Methods
modify
(f: (a: A) => A): (s: S) => S
set
(a: A): (s: S) => S
asFold
(): Fold<S, A>
view a Traversal as a Fold
asSetter
(): Setter<S, A>
view a Traversal as a Setter
compose
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose a Traversal with a Traversal
composeFold
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Traversal with a Fold
composeSetter
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Traversal with a Setter
composeOptional
<B>(ab: Optional<A, B>): Traversal<S, B>
compose a Traversal with a Optional
composeLens
<B>(ab: Lens<A, B>): Traversal<S, B>
compose a Traversal with a Lens
composePrism
<B>(ab: Prism<A, B>): Traversal<S, B>
compose a Traversal with a Prism
composeIso
<B>(ab: Iso<A, B>): Traversal<S, B>
compose a Traversal with a Iso
composeGetter
<B>(ab: Getter<A, B>): Fold<S, B>
compose a Traversal with a Getter
Getter
class Getter<S, A> {
constructor(readonly get: (s: S) => A)
}
Methods
asFold
(): Fold<S, A>
view a Getter as a Fold
compose
<B>(ab: Getter<A, B>): Getter<S, B>
compose a Getter with a Getter
composeFold
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Getter with a Fold
composeLens
<B>(ab: Lens<A, B>): Getter<S, B>
compose a Getter with a Lens
composeIso
<B>(ab: Iso<A, B>): Getter<S, B>
compose a Getter with a Iso
composeTraversal
<B>(ab: Traversal<A, B>): Fold<S, B>
compose a Getter with a Optional
composeOptional
<B>(ab: Optional<A, B>): Fold<S, B>
compose a Getter with a Optional
composePrism
<B>(ab: Prism<A, B>): Fold<S, B>
compose a Getter with a Prism
Fold
class Fold<S, A> {
constructor(readonly foldMap: <M>(M: Monoid<M>) => (f: (a: A) => M) => (s: S) => M)
}
Methods
compose
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Fold with a Fold
composeGetter
<B>(ab: Getter<A, B>): Fold<S, B>
compose a Fold with a Getter
composeTraversal
<B>(ab: Traversal<A, B>): Fold<S, B>
compose a Fold with a Traversal
composeOptional
<B>(ab: Optional<A, B>): Fold<S, B>
compose a Fold with a Optional
composeLens
<B>(ab: Lens<A, B>): Fold<S, B>
compose a Fold with a Lens
composePrism
<B>(ab: Prism<A, B>): Fold<S, B>
compose a Fold with a Prism
composeIso
<B>(ab: Iso<A, B>): Fold<S, B>
compose a Fold with a Iso
find
(p: Predicate<A>): (s: S) => Option<A>
find the first target of a Fold matching the predicate
headOption
(s: S): Option<A>
get the first target of a Fold
getAll
(s: S): Array<A>
get all the targets of a Fold
exist
(p: Predicate<A>): Predicate<S>
check if at least one target satisfies the predicate
all
(p: Predicate<A>): Predicate<S>
check if all targets satisfy the predicate
Setter
class Setter<S, A> {
constructor(readonly modify: (f: (a: A) => A) => (s: S) => S)
}
Methods
set
(a: A): (s: S) => S
compose
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Setter with a Setter
composeTraversal
<B>(ab: Traversal<A, B>): Setter<S, B>
compose a Setter with a Traversal
composeOptional
<B>(ab: Optional<A, B>): Setter<S, B>
compose a Setter with a Optional
composeLens
<B>(ab: Lens<A, B>): Setter<S, B>
compose a Setter with a Lens
composePrism
<B>(ab: Prism<A, B>): Setter<S, B>
compose a Setter with a Prism
composeIso
<B>(ab: Iso<A, B>): Setter<S, B>
compose a Setter with a Iso
fromTraversable
<T>(T: Traversable<T>): <A>() => Traversal<HKT<T, A>, A>
create a Traversal from a Traversable
fromFoldable
<F>(F: Foldable<F>): <A>() => Fold<HKT<F, A>, A>
create a Fold from a Foldable