Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
monocle-ts
Advanced tools
monocle-ts is a library for functional programming in TypeScript that provides tools for working with immutable data structures. It offers optics such as lenses, prisms, and traversals to facilitate the manipulation and querying of deeply nested data in a type-safe manner.
Lens
Lenses are used to focus on a specific part of a data structure. In this example, a lens is created to focus on the 'city' property within the 'address' object of a 'person'. The lens is then used to update the city to 'Los Angeles'.
const { Lens } = require('monocle-ts');
const person = { name: 'John', address: { city: 'New York' } };
const addressLens = Lens.fromPath(['address', 'city']);
const newPerson = addressLens.set('Los Angeles')(person);
console.log(newPerson); // { name: 'John', address: { city: 'Los Angeles' } }
Prism
Prisms are used to focus on a part of a data structure that may or may not be present. In this example, a prism is created to focus on the 'Some' case of an option type. The prism is then used to extract the value if it exists.
const { Prism } = require('monocle-ts');
const some = (value) => ({ _tag: 'Some', value });
const none = { _tag: 'None' };
const optionPrism = Prism.fromPredicate((s) => s._tag === 'Some');
const result = optionPrism.getOption(some(42));
console.log(result); // { _tag: 'Some', value: 42 }
Traversal
Traversals are used to focus on multiple parts of a data structure. In this example, a traversal is created to focus on each element of an array. The traversal is then used to double each number in the array.
const { fromTraversable, array } = require('monocle-ts');
const numbers = [1, 2, 3, 4];
const traversal = fromTraversable(array)();
const newNumbers = traversal.modify((n) => n * 2)(numbers);
console.log(newNumbers); // [2, 4, 6, 8]
Ramda is a practical functional library for JavaScript developers. It provides a wide range of functions for working with immutable data, including lenses and traversals. Compared to monocle-ts, Ramda is more general-purpose and less focused on TypeScript's type safety.
Immutable.js provides persistent immutable data structures for JavaScript. It offers a different approach to immutability by providing its own data structures rather than focusing on optics like monocle-ts. Immutable.js is more about providing efficient immutable collections.
fp-ts is a library for functional programming in TypeScript. It provides a wide range of functional programming tools, including optics similar to those in monocle-ts. fp-ts is more comprehensive and includes many other functional programming utilities beyond optics.
(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')
const address = Lens.fromProp<Company>()('address')
const street = Lens.fromProp<Address>()('street')
const name = Lens.fromProp<Street>()('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
.
The stable version is tested against TypeScript 3.2.2, but should run with TypeScript 2.8.0+ too
Note. If you are running < typescript@3.0.1
you have to polyfill unknown
.
You can use unknown-ts as a polyfill.
class Iso<S, A> {
constructor(readonly get: (s: S) => A, readonly reverseGet: (a: A) => S)
}
;(s: S) => A
Alias of get
;(s: S) => A
Alias of get
;(a: A) => S
Alias of reverseGet
;(a: A) => S
Alias of reverseGet
(): Iso<A, S>
reverse the Iso
: the source becomes the target and the target becomes the source
(f: (a: A) => A): (s: S) => S
(): Lens<S, A>
view an Iso as a Lens
(): Prism<S, A>
view an Iso as a Prism
(): Optional<S, A>
view an Iso as a Optional
(): Traversal<S, A>
view an Iso as a Traversal
(): Fold<S, A>
view an Iso as a Fold
(): Getter<S, A>
view an Iso as a Getter
(): Setter<S, A>
view an Iso as a Setter
<B>(ab: Iso<A, B>): Iso<S, B>
compose an Iso with an Iso
Alias of compose
<B>(ab: Lens<A, B>): Lens<S, B>
compose an Iso with a Lens
<B>(ab: Prism<A, B>): Prism<S, B>
compose an Iso with a Prism
<B>(ab: Optional<A, B>): Optional<S, B>
compose an Iso with an Optional
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose an Iso with a Traversal
<B>(ab: Fold<A, B>): Fold<S, B>
compose an Iso with a Fold
<B>(ab: Getter<A, B>): Getter<S, B>
compose an Iso with a Getter
<B>(ab: Setter<A, B>): Setter<S, B>
compose an Iso with a Setter
class Lens<S, A> {
constructor(readonly get: (s: S) => A, readonly set: (a: A) => (s: S) => S)
}
<S>(): <K1 extends keyof S>(path: [K1]) => Lens<S, S[K1]> // other 4 overloadings
// or (deprecated)
<S, K1 extends keyof S>(path: [K1]): Lens<S, S[K1]> // other 4 overloadings
Example
type Person = {
name: string
age: number
address: {
city: string
}
}
const city = Lens.fromPath<Person>()(['address', 'city'])
const person: Person = { name: 'Giulio', age: 43, address: { city: 'Milan' } }
console.log(city.get(person)) // Milan
console.log(city.set('London')(person)) // { name: 'Giulio', age: 43, address: { city: 'London' } }
<S>(): <P extends keyof S>(prop: P) => Lens<S, S[P]>
// or (deprecated)
<S, P extends keyof T>(prop: P): Lens<S, S[P]>
generate a lens from a type and a prop
Example
type Person = {
name: string
age: number
}
const age = Lens.fromProp<Person>()('age')
// or (deprecated)
// const age = Lens.fromProp<Person, 'age'>('age')
const person: Person = { name: 'Giulio', age: 43 }
console.log(age.get(person)) // 43
console.log(age.set(44)(person)) // { name: 'Giulio', age: 44 }
<S>(): <P extends keyof S>(props: Array<P>) => Lens<S, { [K in P]: S[K] }>
generate a lens from a type and an array of props
Example
interface Person {
name: string
age: number
rememberMe: boolean
}
const lens = Lens.fromProps<Person>()(['name', 'age'])
const person: Person = { name: 'Giulio', age: 44, rememberMe: true }
console.log(lens.get(person)) // { name: 'Giulio', age: 44 }
console.log(lens.set({ name: 'Guido', age: 47 })(person)) // { name: 'Guido', age: 47, rememberMe: true }
<S>(): <A extends S[K], K extends keyof S>(k: K, defaultValue: A) => Lens<S, A>
// or (deprecated)
<S, A extends S[K], K extends keyof S>(k: K, defaultValue: A): Lens<S, A>
generate a lens from a type and a prop whose type is nullable
Example
interface Outer {
inner?: Inner
}
interface Inner {
value: number
foo: string
}
const inner = Lens.fromNullableProp<Outer>()('inner', { value: 0, foo: 'foo' })
const value = Lens.fromProp<Inner>()('value')
const lens = inner.compose(value)
console.log(lens.set(1)({})) // { inner: { value: 1, foo: 'foo' } }
console.log(lens.get({})) // 0
console.log(lens.set(1)({ inner: { value: 1, foo: 'bar' } })) // { inner: { value: 1, foo: 'bar' } }
console.log(lens.get({ inner: { value: 1, foo: 'bar' } })) // 1
(f: (a: A) => A): (s: S) => S
(): Optional<S, A>
view a Lens as a Optional
(): Traversal<S, A>
view a Lens as a Traversal
(): Setter<S, A>
view a Lens as a Setter
(): Getter<S, A>
view a Lens as a Getter
(): Fold<S, A>
view a Lens as a Fold
<B>(ab: Lens<A, B>): Lens<S, B>
compose a Lens with a Lens
Alias of compose
<B>(ab: Getter<A, B>): Getter<S, B>
compose a Lens with a Getter
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Lens with a Fold
<B>(ab: Optional<A, B>): Optional<S, B>
compose a Lens with an Optional
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose a Lens with an Traversal
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Lens with an Setter
<B>(ab: Iso<A, B>): Lens<S, B>
compose a Lens with an Iso
<B>(ab: Prism<A, B>): Optional<S, B>
compose a Lens with a Prism
class Prism<S, A> {
constructor(readonly getOption: (s: S) => Option<A>, readonly reverseGet: (a: A) => S)
}
<A>(predicate: Predicate<A>): Prism<A, A>
<A>(): Prism<Option<A>, A>
(f: (a: A) => A): (s: S) => S
(f: (a: A) => A): (s: S) => Option<S>
(a: A): (s: S) => S
set the target of a Prism with a value
(): Optional<S, A>
view a Prism as a Optional
(): Traversal<S, A>
view a Prism as a Traversal
(): Setter<S, A>
view a Prism as a Setter
(): Fold<S, A>
view a Prism as a Fold
<B>(ab: Prism<A, B>): Prism<S, B>
compose a Prism with a Prism
Alias of compose
<B>(ab: Optional<A, B>): Optional<S, B>
compose a Prism with a Optional
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose a Prism with a Traversal
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Prism with a Fold
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Prism with a Setter
<B>(ab: Iso<A, B>): Prism<S, B>
compose a Prism with a Iso
<B>(ab: Lens<A, B>): Optional<S, B>
compose a Prism with a Lens
<B>(ab: Getter<A, B>): Fold<S, B>
compose a Prism with a Getter
class Optional<S, A> {
constructor(readonly getOption: (s: S) => Option<A>, readonly set: (a: A) => (s: S) => S) {}
}
<S>() <K extends keyof S>(k: K): Optional<S, NonNullable<S[K]>>
// or (deprecated)
<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')
const employment = Optional.fromNullableProp<Info>()('employment')
const phone = Optional.fromNullableProp<Employment>()('phone')
const number = Lens.fromProp<Phone>()('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) // some('555-1234')
numberFromResponse.getOption(response2) // none
<S>(): <P extends OptionPropertyNames<S>>(prop: P) => Optional<S, OptionPropertyType<S, P>>
<S>(prop: OptionPropertyNames<S>) => Optional<S, OptionPropertyType<S, typeof prop>>
Example
interface Phone {
number: string
}
interface Employment {
phone: Option<Phone>
}
interface Info {
employment: Option<Employment>
}
interface Response {
info: Option<Info>
}
const info = Optional.fromOptionProp<Response>('info')
const employment = Optional.fromOptionProp<Info>('employment')
const phone = Optional.fromOptionProp<Employment>('phone')
const number = Lens.fromProp<Phone>()('number')
const numberFromResponse = info
.compose(employment)
.compose(phone)
.composeLens(number)
(f: (a: A) => A): (s: S) => S
(f: (a: A) => A): (s: S) => Option<S>
(): Traversal<S, A>
view a Optional as a Traversal
(): Fold<S, A>
view an Optional as a Fold
(): Setter<S, A>
view an Optional as a Setter
<B>(ab: Optional<A, B>): Optional<S, B>
compose a Optional with a Optional
Alias of compose
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose an Optional with a Traversal
<B>(ab: Fold<A, B>): Fold<S, B>
compose an Optional with a Fold
<B>(ab: Setter<A, B>): Setter<S, B>
compose an Optional with a Setter
<B>(ab: Lens<A, B>): Optional<S, B>
compose an Optional with a Lens
<B>(ab: Prism<A, B>): Optional<S, B>
compose an Optional with a Prism
<B>(ab: Iso<A, B>): Optional<S, B>
compose an Optional with a Iso
<B>(ab: Getter<A, B>): Fold<S, B>
compose an Optional with a Getter
class Traversal<S, A> {
constructor(readonly modifyF: <F>(F: Applicative<F>) => (f: (a: A) => HKT<F, A>) => (s: S) => HKT<F, S>)
}
an optic that focuses on multiple elements in a data structure. See fromTraversable
(f: (a: A) => A): (s: S) => S
modify each element focused by a traversal using the passed function
(a: A): (s: S) => S
set the value of each element focused by the traversal
(): Fold<S, A>
view a Traversal as a Fold
(): Setter<S, A>
view a Traversal as a Setter
<B>(ab: Traversal<A, B>): Traversal<S, B>
compose a Traversal with another Traversal
Alias of compose
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Traversal with a Fold
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Traversal with a Setter
<B>(ab: Optional<A, B>): Traversal<S, B>
compose a Traversal with a Optional
<B>(ab: Lens<A, B>): Traversal<S, B>
compose a Traversal with a Lens
<B>(ab: Prism<A, B>): Traversal<S, B>
compose a Traversal with a Prism
<B>(ab: Iso<A, B>): Traversal<S, B>
compose a Traversal with a Iso
<B>(ab: Getter<A, B>): Fold<S, B>
compose a Traversal with a Getter
class Getter<S, A> {
constructor(readonly get: (s: S) => A)
}
(): Fold<S, A>
view a Getter as a Fold
<B>(ab: Getter<A, B>): Getter<S, B>
compose a Getter with a Getter
Alias of compose
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Getter with a Fold
<B>(ab: Lens<A, B>): Getter<S, B>
compose a Getter with a Lens
<B>(ab: Iso<A, B>): Getter<S, B>
compose a Getter with a Iso
<B>(ab: Traversal<A, B>): Fold<S, B>
compose a Getter with a Optional
<B>(ab: Optional<A, B>): Fold<S, B>
compose a Getter with a Optional
<B>(ab: Prism<A, B>): Fold<S, B>
compose a Getter with a Prism
class Fold<S, A> {
constructor(readonly foldMap: <M>(M: Monoid<M>) => (f: (a: A) => M) => (s: S) => M)
}
<B>(ab: Fold<A, B>): Fold<S, B>
compose a Fold with a Fold
Alias of compose
<B>(ab: Getter<A, B>): Fold<S, B>
compose a Fold with a Getter
<B>(ab: Traversal<A, B>): Fold<S, B>
compose a Fold with a Traversal
<B>(ab: Optional<A, B>): Fold<S, B>
compose a Fold with a Optional
<B>(ab: Lens<A, B>): Fold<S, B>
compose a Fold with a Lens
<B>(ab: Prism<A, B>): Fold<S, B>
compose a Fold with a Prism
<B>(ab: Iso<A, B>): Fold<S, B>
compose a Fold with a Iso
(p: Predicate<A>): (s: S) => Option<A>
find the first target of a Fold matching the predicate
(s: S): Option<A>
get the first target of a Fold
(s: S): Array<A>
get all the targets of a Fold
(p: Predicate<A>): Predicate<S>
check if at least one target satisfies the predicate
(p: Predicate<A>): Predicate<S>
check if all targets satisfy the predicate
class Setter<S, A> {
constructor(readonly modify: (f: (a: A) => A) => (s: S) => S)
}
(a: A): (s: S) => S
<B>(ab: Setter<A, B>): Setter<S, B>
compose a Setter with a Setter
Alias of compose
<B>(ab: Traversal<A, B>): Setter<S, B>
compose a Setter with a Traversal
<B>(ab: Optional<A, B>): Setter<S, B>
compose a Setter with a Optional
<B>(ab: Lens<A, B>): Setter<S, B>
compose a Setter with a Lens
<B>(ab: Prism<A, B>): Setter<S, B>
compose a Setter with a Prism
<B>(ab: Iso<A, B>): Setter<S, B>
compose a Setter with a Iso
<T>(T: Traversable<T>): <A>() => Traversal<HKT<T, A>, A>
create a Traversal from a Traversable
Example: reversing strings in a nested array
import { Lens, fromTraversable } from 'monocle-ts'
import { array } from 'fp-ts/lib/Array'
interface Tweet {
text: string
}
interface Tweets {
tweets: Tweet[]
}
const tweetsLens = Lens.fromProp<Tweets>()('tweets')
const tweetTextLens = Lens.fromProp<Tweet>()('text')
const tweetTraversal = fromTraversable(array)<Tweet>()
const composedTraversal = tweetsLens.composeTraversal(tweetTraversal).composeLens(tweetTextLens)
const tweet1: Tweet = { text: 'hello world' }
const tweet2: Tweet = { text: 'foobar' }
const model: Tweets = { tweets: [tweet1, tweet2] }
const newModel = composedTraversal.modify(text =>
text
.split('')
.reverse()
.join('')
)(model)
// { tweets: [ { text: 'dlrow olleh' }, { text: 'raboof' } ] }
<F>(F: Foldable<F>): <A>() => Fold<HKT<F, A>, A>
create a Fold from a Foldable
class At<S, I, A> {
constructor(readonly at: (i: I) => Lens<S, A>) {}
}
lift an instance of At
using an Iso
<T>(iso: Iso<T, S>): At<T, I, A>
<A = never>(setoid: Setoid<A>): At<Set<A>, A, boolean>
<A = never>(): At<SM.StrMap<A>, string, Option<A>>
class Index<S, I, A> {
constructor(readonly index: (i: I) => Optional<S, A>) {}
}
<T, J, B>(at: At<T, J, Option<B>>): Index<T, J, B>
lift an instance of Index
using an Iso
<T>(iso: Iso<T, S>): Index<T, I, A>
<A = never>(): Index<Array<A>, number, A>
<A = never>(): Index<NonEmptyArray<A>, number, A>
<A = never>(): Index<StrMap<A>, string, A>
FAQs
A porting of scala monocle library to TypeScript
The npm package monocle-ts receives a total of 326,140 weekly downloads. As such, monocle-ts popularity was classified as popular.
We found that monocle-ts demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.