TypeScript Lens implementation with property proxy
npm install lens.tsTypeScript Lens implementation with property proxy
Lens is composable abstraction of getter and setter. For more detail of Lens, I
recommend reading the following documents.
- Haskell lens package
- A Little Lens Starter Tutorial of School of Haskell
Via npm:
`` shell`
npm i lens.ts
` 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;
};
const azusa: Person = {
name: 'Nakano Azusa',
age: 15,
accounts: [
{
type: 'twitter',
handle: '@azusa'
},
{
type: 'facebook',
handle: 'nakano.azusa'
}
]
};
// create an identity lens for Person
const 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
const fstAccountL = lens
const handleL = 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.
lens.ts exports the followings:
` typescript`
import {
lens,
Getter,
Setter,
Lens,
createLens
} from 'lens.ts';
A function lens is a factory function for an identity lens for a type. ItLens
returns a instance.
` typescript`
lens
They are just a type alias for the following function types.
` typescript`
export type Getter
export type Setter
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.
An instance of Lens can be constructed with a getter and setter for aT
source type and a result type U.
Lens is not just a class, but it's internally a product type of LensImpl andnew Lens()
Proxy types, so you cannot just create one with . A recommended waylens
to create an instance is , but you can also manually provide a gettercreateLens()
and a setter with .
` typescript`
function createLens
_get: Getter
_set: (value: U) => Setter
): Lens
return proxify(new LensImpl(_get, _set));
}
Lens provides the following methods.
#### .k
Narrow the lens for a property of U.
` typescript
// we will use these types for the following examples
type Person = {
name: string;
age: number;
accounts: Account[];
};
lens
lens
lens
`
#### .get()
It is polymorphic.
- .get(): Getter.get
-
.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
const target = { age: 15, ... };
const ageL = lens
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 returnsSetter
an updated (and new) object. s here should be all immutable. You can
provide a value to set, or optionally a setter for value.
` typescript
const target = { age: 15, ... };
const ageL = lens
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
let firstL =
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)
Property proxy couldn't have been implemented without
@ktsn's help.
- https://github.com/ktsn/lens-proxy