https://github.com/hatashiro/lens.ts
TypeScript Lens implementation with property proxy
https://github.com/hatashiro/lens.ts
lens typescript
Last synced: 7 months ago
JSON representation
TypeScript Lens implementation with property proxy
- Host: GitHub
- URL: https://github.com/hatashiro/lens.ts
- Owner: hatashiro
- License: mit
- Created: 2017-08-22T09:25:30.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2019-08-26T15:35:09.000Z (about 6 years ago)
- Last Synced: 2024-10-29T20:10:20.631Z (11 months ago)
- Topics: lens, typescript
- Language: TypeScript
- Homepage:
- Size: 35.2 KB
- Stars: 143
- Watchers: 3
- Forks: 15
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# lens.ts [](https://travis-ci.org/utatti/lens.ts)
TypeScript Lens implementation with property proxy
## Lens?
Lens is composable abstraction of getter and setter. For more detail of Lens, I
recommend reading the following documents.- [**Haskell `lens` package**](https://hackage.haskell.org/package/lens)
- [**A Little Lens Starter Tutorial**](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial) of *School of Haskell*## Install
Via [npm](https://www.npmjs.com/package/lens.ts):
```shell
npm i lens.ts
```## Usage
```typescript
// import a factory function for lens
import { lens } from 'lens.ts';type Person = {
name: string;
age: number;
accounts: Array;
};type Account = {
type: string;
handle: string;
};let azusa: Person = {
name: 'Nakano Azusa',
age: 15,
accounts: [
{
type: 'twitter',
handle: '@azusa'
},
{
type: 'facebook',
handle: 'nakano.azusa'
}
]
};// create an identity lens for Person
let personL = lens();// key lens with k()
personL.k('name') // :: Lens
personL.k('accounts') // :: Lens>
personL.k('hoge') // type error, 'hoge' is not a key of Person
personL.k('accounts').k(1) // :: Lens
personL.k(1) // type error, 'i' cannot be used for non-array type// You can use property proxy to narrow lenses
personL.name // :: Lens
personL.accounts // :: Lens>
personL.accounts[1] // :: Lens
personL.hoge // type error// get and set with Lens
personL.accounts[0].handle.get()(azusa) // -> '@azusa'
personL.accounts[0].handle.set('@nakano')(azusa) // -> { ... { handle: '@nakano' } ... }
personL.age.set(x => x + 1)(azusa) // -> { age: 16, ... }// Lens composition
let fstAccountL = lens().accounts[0] // :: Lens
let handleL = lens().handle // :: Lens
fstAccountL.compose(handleL) // :: Lens// Getter/Setter composition
fstAccountL.get(handleL.get())(azusa) // -> '@azusa'
fstAccountL.set(handleL.set('@nakano'))(azusa) // -> { ... { handle: '@nakano' } ... }
```You can find similar example code in [/test](test).
## API
`lens.ts` exports the followings:
```typescript
import {
lens,
Getter,
Setter,
Lens
} from 'lens.ts';
```### `lens`
A function `lens` is a factory function for a lens. Without any arguments
except for a type parameter, it returns an identity lens for the provided
type.```typescript
lens() // :: Lens
```You can provide a getter and a setter to create a lens manually. They should
have proper `Getter` and `Setter` types.```typescript
let getter // :: Getter
let setter // :: (val: Y) => Setter
lens(getter, setter) // :: Lens
```### `Getter`, `Setter`
They are just a type alias for the following function types.
```typescript
export type Getter = (target: T) => V;
export type Setter = (target: T) => T;
```Basically, `Getter` is a function to retrieve a value from a target. `Setter` is
a function to set or update a value in a provided target and return a new object
with a same type as the target.Any `Setter` returned from `Lens` has immutable API, which means it doesn't
modify the target object.### `Lens`
A lens is consisted of a getter and a setter for a source type `T` and a
result type `U`.`Lens` is not a class, so it can't be created with `new Lens()`. It's
internally a product type of `LensImpl` and `LensProxy`. Please use `lens()`
to create a lens.`Lens` provides the following methods.
#### `.k(key: K)`
Narrow the lens for a property or an index of `U`.
```typescript
// we will use these types for the following examples
type Person = {
name: string;
age: number;
accounts: Account[];
};lens().k('name') // :: Lens
lens().k('accounts') // :: Lens
lens().k('accounts').k(0) // :: Lens
```#### `.get()`
It is polymorphic.
- `.get(): Getter`
- `.get(f: Getter): Getter``.get()` returns a getter, which can be applied to an actual target object to
retrive an actual value. You can optionally provide another getter (or mapping
function) to retrieve a mapped value.```typescript
let target = { age: 15, ... };let ageL = lens().k('age');
ageL.get()(target) // -> 15
ageL.get(age => age + 10)(target) // -> 25
```#### `.set()`
It is polymorphic.
- `.set(val: U): Setter`
- `.set(f: Setter): Setter``.set()` returns a setter, which can set or update an internal value and returns
an updated (and new) object. `Setter`s here should be all immutable. You can
provide a value to set, or optionally a setter for value.```typescript
let target = { age: 15, ... };let ageL = lens().k('age');
ageL.set(20)(target) // -> { age: 20, ... }
ageL.set(age => age + 1)(target) // -> { age: 16, ... }
```#### `.compose(another: Lens): Lens`
Compose 2 lenses into one.
```typescript
let lens1: Lens;
let lens2: Lens;let accountsL = lens().k('accounts');
let firstL = () => lens().k(0);let firstAccountL =
accountsL.compose(firstL()); // :: Lens
```*FYI: The reason `firstL` becomes a function with `` is to make it
polymorphic.*#### Proxied properties
`Lens` also provides proxied properties for the type `U`.
```typescript
objL.name // same as objL.k('name')
arrL[0] // same as arrL.k(0)
```Note that proxy objects are not available in Internet Explorer as [caniuse describes](https://caniuse.com/#feat=proxy).
## Credits
Property proxy couldn't have been implemented without
[@ktsn](https://github.com/ktsn)'s help.- https://github.com/ktsn/lens-proxy
## License
[MIT](LICENSE)