Skip to content

Instantly share code, notes, and snippets.

@hatashiro
Last active April 16, 2018 02:05
Show Gist options
  • Save hatashiro/50b67ce4c3129b2d57085054f6bc1f7e to your computer and use it in GitHub Desktop.
Save hatashiro/50b67ce4c3129b2d57085054f6bc1f7e to your computer and use it in GitHub Desktop.
TypeScript Lens implementation with object property proxy
/*
* TypeScript Lens implementation with object property proxy
*
* ref:
*
* - Haskell Lens (https://hackage.haskell.org/package/lens)
* - Type-safe Lens by @mitaki28 (https://gist.github.com/mitaki28/ad39a69ab4fa73c99a822c0c3abc99dd)
*
*/
class LensInternal<T, U> {
constructor(
public __get: (from: T) => U,
public __set: (val: U) => (from: T) => T,
) { }
_<V>(other: Lens<U, V>): Lens<T, V> {
return lens(
(x: T) => other.__get(this.__get(x)),
(x: V) => (o: T) => this.__set(other.__set(x)(this.__get(o)))(o),
);
}
}
type Lens<T, U> = {
readonly [K in keyof U]: Lens<T, U[K]>;
} & LensInternal<T, U>;
function lens<T, U>(
__get: (from: T) => U,
__set: (val: U) => (from: T) => T,
): Lens<T, U> {
return new Proxy(new LensInternal(__get, __set), {
get(lens: Lens<T, U>, k: keyof Lens<T, U>) {
return k in lens ? lens[k] : lens._(key<U>()(k as keyof U));
}
}) as Lens<T, U>;
}
function id<T>(): Lens<T, T> {
return lens(
x => x,
x => _ => x,
);
}
function key<T>(): <K extends keyof T>(k: K) => Lens<T, T[K]> {
return k => lens(
o => o[k],
x => o => Object.assign(o, { [k]: x }),
);
}
function getl<T, U>(lens: Lens<T, U>, target: T): U {
return lens.__get(target);
}
function setl<T, U>(lens: Lens<T, U>, val: U, target: T): T {
return lens.__set(val)(target);
}
/*
* Example code
*/
type Person = {
name: string,
age: number,
accounts: Accounts,
};
type Accounts = {
twitter?: string,
facebook?: string,
};
const azusa: Person = {
name: 'Azusa',
age: 15,
accounts: {
twitter: '@azusa',
},
};
// property lenses are created dynamically with Proxy
const twitterLens = id<Person>().accounts.twitter;
console.log(getl(twitterLens, azusa)); // -> '@azusa'
setl(twitterLens, '@nakano', azusa);
console.log(getl(
id<Person>().accounts
// ._() can be used to compose lenses
._(key<Accounts>()('twitter')),
azusa,
)); // -> '@nakano'
/*
* Type errors
*/
type Person = {
name: string,
age: number,
accounts: Accounts,
};
type Accounts = {
twitter?: string,
facebook?: string,
};
// all the following lines result in type errors
id<Person>().foo;
id<Person>()._(key<Person>()('foo'));
id<Person>()._(key<Person>()('accounts'))._(key<Accounts>()('foo'));
@hatashiro
Copy link
Author

Implemented as a package lens.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment