@typed-f/lens
TypeScript icon, indicating that this package has built-in type declarations

0.3.8 • Public • Published

@typed-f/lens

NPM Version repo-circleci-badge: https://img.shields.io/circleci/project/github/Ailrun/typed-f/master.svg?logo=circleci Known Vulnerabilities Supported TypeScript Version PRs Welcome

Watch on GitHub Star on GitHub

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 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');

Package Sidebar

Install

npm i @typed-f/lens

Weekly Downloads

52

Version

0.3.8

License

MIT

Unpacked Size

31.9 kB

Total Files

12

Last publish

Collaborators

  • ailrun