UNPKG

@typed-f/lens

Version:

[![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

335 lines (285 loc) 16.1 kB
# @typed-f/lens [![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-supported-typescript-badge]][supported-typescript] [![PRs Welcome][prs-welcome-badge]][prs-welcome] [![Watch on GitHub][repo-github-watch-badge]][repo-github-watch] [![Star on GitHub][repo-github-star-badge]][repo-github-star] Lens for [Typed-F][repo-github] ## Installation ```shell # 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. ``` typescript 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. ``` typescript 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. ``` typescript 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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) (Be careful about [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Browser_compatibility)! You may consider using [this polyfill](https://github.com/GoogleChrome/proxy-polyfill#usage).) ``` typescript 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. ``` typescript const objectLens = new LensGenerator<typeof oldObject>().byProxy(); const newObject = objectLens.user.books[0].price.set()(oldObject)(22); ``` ## APIs This package includes following classes - [`Lens`](#constructor-of-lens) - [`LensS`](#constructor-of-lenss) (Simple Lens) - [`LensGenerator`](#constructor-of-lensgenerator) `Lens` and `LensS` (specially `Lens`) just include some boring APIs, and really useful utilities come from `LensGenerator`. ### Methods of `Lens` #### Constructor of `Lens` ```typescript 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` ```typescript 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` ```typescript 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` ```typescript 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` ```typescript constructor(get: Fun<[S], A>, set: Fun<[S], Fun<[A], S>>) ``` Same with `Lens` constructor except that this one has fewer type parameters. #### `makeInner` ```typescript 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 ```typescript 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, ```typescript // 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` ```typescript 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 ```typescript 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` ```typescript constructor() ``` Constructor for `LensGenerator` is only for type parameter. See [this issue](https://github.com/Ailrun/typed-f/issues/30) for discussion about this. #### `fromKey` ```typescript 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` ```typescript 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` ```typescript byProxy(): LensSProxy<S, S> ``` *Warning: this API uses ES6 [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). Be careful about [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Browser_compatibility)! [This polyfill](https://github.com/GoogleChrome/proxy-polyfill#usage) 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 ```typescript 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'); ``` [lerna-badge]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg [lerna]: https://lernajs.io/ [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg [prs-welcome]: http://makeapullrequest.com [repo-supported-typescript-badge]: https://img.shields.io/badge/support-typescript%40%3E%3D3.0-007acc.svg [supported-typescript]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html [repo-slack-badge]: https://typed-f.now.sh/slack/badge.svg?style=for-the-badge&logo=slack [repo-slack]: https://typed-f.now.sh/slack/welcome [repo-circleci]: https://circleci.com/gh/Ailrun/typed-f/tree/master [repo-circleci-badge]: https://img.shields.io/circleci/project/github/Ailrun/typed-f/master.svg?logo=circleci [repo-github]: https://github.com/Ailrun/typed-f [repo-github-releases]: https://github.com/Ailrun/typed-f/releases [repo-github-watch]: https://github.com/Ailrun/typed-f/watchers [repo-github-star]: https://github.com/Ailrun/typed-f/stargazers [repo-github-license-badge]: https://img.shields.io/github/license/Ailrun/typed-f.svg [repo-github-tag-badge]: https://img.shields.io/github/tag/Ailrun/typed-f.svg?colorB=blue [repo-github-watch-badge]: https://img.shields.io/github/watchers/Ailrun/typed-f.svg?style=social [repo-github-star-badge]: https://img.shields.io/github/stars/Ailrun/typed-f.svg?style=social [applicative-github]: https://github.com/Ailrun/typed-f/tree/master/packages/applicative [either-github]: https://github.com/Ailrun/typed-f/tree/master/packages/either [function-github]: https://github.com/Ailrun/typed-f/tree/master/packages/function [functor-github]: https://github.com/Ailrun/typed-f/tree/master/packages/functor [lens-github]: https://github.com/Ailrun/typed-f/tree/master/packages/lens [matchable-github]: https://github.com/Ailrun/typed-f/tree/master/packages/matchable [maybe-github]: https://github.com/Ailrun/typed-f/tree/master/packages/maybe [monad-github]: https://github.com/Ailrun/typed-f/tree/master/packages/monad [setoid-github]: https://github.com/Ailrun/typed-f/tree/master/packages/setoid [tagged-github]: https://github.com/Ailrun/typed-f/tree/master/packages/tagged [applicative-npm]: https://www.npmjs.com/package/@typed-f/applicative [either-npm]: https://www.npmjs.com/package/@typed-f/either [function-npm]: https://www.npmjs.com/package/@typed-f/function [functor-npm]: https://www.npmjs.com/package/@typed-f/functor [lens-npm]: https://www.npmjs.com/package/@typed-f/lens [matchable-npm]: https://www.npmjs.com/package/@typed-f/matchable [maybe-npm]: https://www.npmjs.com/package/@typed-f/maybe [monad-npm]: https://www.npmjs.com/package/@typed-f/monad [setoid-npm]: https://www.npmjs.com/package/@typed-f/setoid [tagged-npm]: https://www.npmjs.com/package/@typed-f/tagged [applicative-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Fapplicative%2Fpackage.json [either-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Feither%2Fpackage.json [function-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Ffunction%2Fpackage.json [functor-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Ffunctor%2Fpackage.json [lens-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Flens%2Fpackage.json [matchable-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Fmatchable%2Fpackage.json [maybe-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Fmaybe%2Fpackage.json [monad-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Fmonad%2Fpackage.json [setoid-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Fsetoid%2Fpackage.json [tagged-snyk-badge]: https://snyk.io/test/github/Ailrun/typed-f/badge.svg?targetFile=packages%2Ftagged%2Fpackage.json [applicative-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/applicative/latest.svg?logo=npm&label=latest&colorB=blue [either-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/either/latest.svg?logo=npm&label=latest&colorB=blue [function-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/function/latest.svg?logo=npm&label=latest&colorB=blue [functor-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/functor/latest.svg?logo=npm&label=latest&colorB=blue [lens-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/lens/latest.svg?logo=npm&label=latest&colorB=blue [matchable-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/matchable/latest.svg?logo=npm&label=latest&colorB=blue [maybe-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/maybe/latest.svg?logo=npm&label=latest&colorB=blue [monad-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/monad/latest.svg?logo=npm&label=latest&colorB=blue [setoid-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/setoid/latest.svg?logo=npm&label=latest&colorB=blue [tagged-npm-version-badge]: https://img.shields.io/npm/v/@typed-f/tagged/latest.svg?logo=npm&label=latest&colorB=blue [applicative-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [either-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [function-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [functor-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [lens-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [matchable-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [maybe-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [monad-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [setoid-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json [tagged-snyk]: https://snyk.io/test/github/Ailrun/typed-f?targetFile=packages%2Flens%2Fpackage.json