@typed-f/lens
v0.3.8
Published
[![NPM Version][lens-npm-version-badge]][lens-npm] [repo-circleci-badge]: https://img.shields.io/circleci/project/github/Ailrun/typed-f/master.svg?logo=circleci [![Known Vulnerabilities][lens-snyk-badge]][lens-snyk] [![Supported TypeScript Version][repo-s
Downloads
122
Maintainers
Readme
@typed-f/lens
repo-circleci-badge: https://img.shields.io/circleci/project/github/Ailrun/typed-f/master.svg?logo=circleci
Lens for Typed-F
Installation
# for NPM>=5
npm install @typed-f/lens
# or
npm i @typed-f/lens
# for NPM<5
npm install --save @typed-f/lens
# or
npm i -S @typed-f/lens
Usage Example
Suppose that you have complex object, and want to update part of it, immutably. For example, you want to update oldObject.user.books[0].price
to 22
in following code, without mutable modification of the object.
const oldObject = {
info: 4,
user: {
id: 4,
books: [
{
id: 23,
title: 'First Book',
publisher: {
id: 123,
name: 'Some Pub',
},
price: 20,
},
{
id: 43,
title: 'Other Book',
publisher: {
id: 154,
name: 'Pub Beer',
},
price: 32,
},
],
},
};
You would do something like this.
const newObject = {
...oldObject,
user: {
...oldObject.user,
books: [
{
...oldObject.user.books[0],
price: 22,
},
oldObject.user.books[1],
],
},
};
What really important is just price: 22
part, but you need many boilerplates to do that. With Lens, you can achieve this with following code.
const objectLens = new LensGenerator<typeof oldObject>().fromKeys();
const newObject = objectLens
.focusTo('user')
.focusTo('books')
.focusTo(0)
.focusTo('price')
.set()(oldObject)(22);
You can write even nicer (well, actually it's matter of taste :) ) syntax with ES6 Proxy
(Be careful about browser compatibility! You may consider using this polyfill.)
const objectLens = new LensGenerator<typeof oldObject>().byProxy();
const newObject = objectLens
.user
.books[0]
.price
.set()(oldObject)(22);
I added some linefeeds to be visually pleasing. However, of course, you can write those accesses in a line.
const objectLens = new LensGenerator<typeof oldObject>().byProxy();
const newObject = objectLens.user.books[0].price.set()(oldObject)(22);
APIs
This package includes following classes
Lens
LensS
(Simple Lens)LensGenerator
Lens
and LensS
(specially Lens
) just include some boring APIs, and really useful utilities come from LensGenerator
.
Methods of Lens
Constructor of Lens
constructor(get: Fun<[S], A>, set: Fun<[S], Fun<[B], T>>)
Making a lens with getter function and setter function. Those functions will be invoked by lens.get()
and lens.set()
.
Type parameter A
is type for the part of S
you want to focus in, and S
is type for whole state.
B
is type for modified value for position of A
, and T
is type for result whole state of modification.
get
get(this: Lens<A, S, B, T>): Fun<[S], A>
Returns getter that this
lens has. Getter will take initial state (S
) and return a value focused in (A
).
set
set(this: Lens<A, S, B, T>): Fun<[S], Fun<[B], T>>
Returns setter that this
lens has. Setter will take initial state (S
) and new value (B
), and return a new state (T
).
map
map(this: Lens<A, S, B, T>): Fun<[S], Fun<[Fun<[A], B>], T>>
Returns a mapper (function that gets a value and uses that value to generate new value) for this
lens.
Methods of LensS
This class inherits Lens
, so it has all Lens
APIs. More specifically, LensS<A, S> extends Lens<A, S, A, S>
.
Constructor of LensS
constructor(get: Fun<[S], A>, set: Fun<[S], Fun<[A], S>>)
Same with Lens
constructor except that this one has fewer type parameters.
makeInner
makeInner<K extends keyof A>(this: LensS<A, S>, key: K): LensS<A[K], A>
Returns new Lens that treat A
as a whole state and A[K]
as a focused target. For example, when you have some object like
interface Manager {
name: string;
}
interface Library {
manager: Manager;
}
interface Obj {
library: Library;
}
const obj: Obj = { library: { manager: { name: 'whoever' } } };
and you have a lens libraryFromObjLens: LensS<Library, Obj>
, calling libraryFromObjLens.makeInner('manager')
will give you managerFromLibraryLens: LensS<Manager, Library>
, i.e., a lens to get manager
from library
and set manager
in library
. For instance,
// Getting result will be `{ manager: { name: 'whoever' } }`, i.e., `Library` object.
libraryFromObjLens.get()(obj);
// Setting result will be `{ library: { manager: { name: 'oh-my' } } }`, i.e., `Obj` object.
libraryFromObjLens.set()(obj)({ manager: { name: 'oh-my' } });
// Getting result will be `{ name: 'whoever' }`, i.e., `Manager` object.
managerFromObjLens.get()(obj.library);
// Setting result will be `{ manager: { name: 'pardon?' } }`, i.e., `Library` object.
managerFromObjLens.set()(obj.library)({ name: 'pardon?' });
// Following get will gives a type error.
managerFromObjLens.get()(obj);
// Following set will gives a type error.
managerFromObjLens.set()(obj)({ name: 'pardon?' });
If you want to make LensS<Manager, Obj>
, i.e., a lens to get library.author
from obj
, and to modify library.author
in obj
, see focusTo
.
focusTo
focusTo<K extends keyof A>(this: LensS<A, S>, key: K): LensS<A[K], S>
Returns new Lens that goes deeper to A[K]
. For example, when you have some object like
interface Manager {
name: string;
}
interface Library {
manager: Manager;
}
interface Obj {
library: Library;
}
declare const obj: Obj;
and you have a lens libraryFromObjLens: LensS<Library, Obj>
, calling libraryFromObjLens.focusTo('manager')
will give you managerFromObjLens: LensS<Manager, Obj>
, i.e., a lens to get manager
from obj
and set manager
in obj
. To make a lens for focusing in Manager
from Library
, see makeInner
.
Methods of LensGenerator
In following type signatures, S
comes from LensGenerator
class, i.e., LensGenerator<S>
is this
. However, I will not write this: LensGenerator<S>
in type signatures, since following methods do not use this
.
Constructor of LensGenerator
constructor()
Constructor for LensGenerator
is only for type parameter. See this issue for discussion about this.
fromKey
fromKey<K extends keyof S>(key: K): LensS<S[K], S>
Construct a LensS
to access state[key]
and to set the value of state[key]
in state: S
.
fromKeys
fromKeys(...keys: []): LensS<S, S>
fromKeys<K0 extends keyof S>(...keys: [K0]): LensS<S[K0], S>
fromKeys<K0 extends keyof S, K1 extends keyof S[K0]>(...keys: [K0, K1]): LensS<S[K0][K1], S>
fromKeys<K0 extends keyof S, K1 extends keyof S[K0], K2 extends keyof S[K0][K1]>(...keys: [K0, K1, K2]): LensS<S[K0][K1][K2], S>
fromKeys<K0 extends keyof S, K1 extends keyof S[K0], K2 extends keyof S[K0][K1], K3 extends keyof S[K0][K1][K2]>(...keys: [K0, K1, K2, K3]): LensS<S[K0][K1][K2][K3], S>
fromKeys<K0 extends keyof S, K1 extends keyof S[K0], K2 extends keyof S[K0][K1], K3 extends keyof S[K0][K1][K2], K4 extends keyof S[K0][K1][K2][K3]>(...keys: [K0, K1, K2, K3, K4]): LensS<S[K0][K1][K2][K3][K4], S>
fromKeys<K0 extends keyof S, K1 extends keyof S[K0], K2 extends keyof S[K0][K1], K3 extends keyof S[K0][K1][K2], K4 extends keyof S[K0][K1][K2][K3], K5 extends keyof S[K0][K1][K2][K3][K4]>(...keys: [K0, K1, K2, K3, K4, K5]): LensS<S[K0][K1][K2][K3][K4][K5], S>
fromKeys<K0 extends keyof S, K1 extends keyof S[K0], K2 extends keyof S[K0][K1], K3 extends keyof S[K0][K1][K2], K4 extends keyof S[K0][K1][K2][K3], K5 extends keyof S[K0][K1][K2][K3][K4], K6 extends keyof S[K0][K1][K2][K3][K4][K5]>(...keys: [K0, K1, K2, K3, K4, K5, K6]): LensS<S[K0][K1][K2][K3][K4][K5][K6], S>
fromKeys<P>(...keys: any[]): LensS<P, S>
Construct a LensS
to access state[keys[0]][keys[1]]...
and set the value of it in state: S
.
byProxy
byProxy(): LensSProxy<S, S>
Warning: this API uses ES6 Proxy
. Be careful about browser compatibility! This polyfill also does not work for this since this API uses dynamic properties.
Returns a Proxy
object that allow you to make lens for any depth of properties. For example, when you have obj: Obj
(Obj
type from the example of makeInner
), you can use
const objProxy = new LensGenerator<Obj>().byProxy();
const nameProxy = objProxy
.library // this can be used as `LensS<Library, Obj>`
.manager // this can be used as `LensS<Manager, Obj>`
.name; // this can be used as `LensS<string, Obj>`
// Setting will returns `{ library: { manager: { name: '123' } } }`, i.e., `Obj` object.
nameProxy.set()(obj)('123');