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 - A Little Lens Starter Tutorial of School of Haskell
Install
Via npm:
npm i lens.ts
Usage
// import a factory function for lens;;;;// create an identity lens for Person;// key lens with k()personL.k'name' // :: Lens<Person, string>personL.k'accounts' // :: Lens<Person, Array<Account>>personL.k'hoge' // type error, 'hoge' is not a key of PersonpersonL.k'accounts'.k1 // :: Lens<Person, Account>personL.k1 // type error, 'i' cannot be used for non-array type// You can use property proxy to narrow lensespersonL.name // :: Lens<Person, string>personL.accounts // :: Lens<Person, Array<Account>>personL.accounts // :: Lens<Person, Account>personL.hoge // type error// get and set with LenspersonL.accounts.handle.getazusa // -> '@azusa'personL.accounts.handle.set'@nakano'azusa // -> { ... { handle: '@nakano' } ... }personL.age.setx + 1azusa // -> { age: 16, ... }// Lens compositionfstAccountL.composehandleL // :: Lens<Person, string>// Getter/Setter compositionfstAccountL.gethandleL.getazusa // -> '@azusa'fstAccountL.sethandleL.set'@nakano'azusa // -> { ... { handle: '@nakano' } ... }
You can find similar example code in /test.
API
lens.ts
exports the followings:
;
lens
A function lens
is a factory function for an identity lens for a type. It
returns a Lens
instance.
lens // :: Lens<Person, Person>
Getter
, Setter
They are just a type alias for the following function types.
;;
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<T, U>
An instance of Lens
can be constructed with a getter and setter for a
source type T
and a result type U
.
Lens
is not just a class, but it's internally a product type of LensImpl
and
Proxy types, so you cannot just create one with new Lens()
. A recommended way
to create an instance is lens<X>()
, but you can also manually provide a getter
and a setter with createLens()
.
Lens
provides the following methods.
.k<K extends keyof U>(key: K)
Narrow the lens for a property of U
.
// we will use these types for the following examples;lens<Person>.k'name' // :: Lens<Person, string>lens<Person>.k'accounts' // :: Lens<Person, Account[]>lens<Person>.k'accounts'.k0 // :: Lens<Person, Account>
.get()
It is polymorphic.
.get(): Getter<T, U>
.get<V>(f: Getter<U, V>): Getter<T, V>
.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.
;;ageL.gettarget // -> 15ageL.getage + 10target // -> 25
.set()
It is polymorphic.
.set(val: U): Setter<T>
.set(f: Setter<U>): Setter<T>
.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.
;;ageL.set20target // -> { age: 20, ... }ageL.setage + 1target // -> { age: 16, ... }
.compose(another: Lens<U, V>): Lens<U, V>
Compose 2 lenses into one.
;;;;; // :: Lens<Person, Account>
FYI: The reason firstL
becomes a function with <T>
is to make it
polymorphic.
Proxied properties
Lens<T, U>
also provides proxied properties for the type U
.
objL.name // same as objL.k('name')arrL // same as arrL.k(0)
Credits
Property proxy couldn't have been implemented without @ktsn's help.