Well-typed functional lenses
yarn add @typed/lenses
# or
npm install --save @typed/lenses
All functions are curried!
A common interface for Updating objects
export interface Lens<A, B> {
readonly view: (object: A) => Maybe<B>
readonly updateAt: LensUpdateAt<A, B>
}
export type LensUpdateAt<A, B> = {
(f: (previousValue: Maybe<B>) => Maybe<B>, object: A): A
(f: (previousValue: Maybe<B>) => Maybe<B>): (object: A) => A
}
Right-to-left lens composition.
See the code
export const composeLenses: ComposeLenses = function(
...lenses: Array<Lens<any, any>>
): Lens<any, any> {
return pipeLenses.apply(this, lenses.reverse())
}
Given a getter and a setter function, it returns a Lens.
See the code
export const lens: LensFn = curry2(__lens)
function __lens<A, B>(getter: (a: A) => B | void, setter: (value: B, a: A) => A): Lens<A, B> {
function updateAt(f: (value: Maybe<B>) => Maybe<B>, a: A): A {
const value = f(view(a))
if (isNothing(value)) return a
return setter(fromJust(value), a)
}
function view(a: A): Maybe<B> {
return Maybe.of(getter(a))
}
return { view, updateAt: curry2(updateAt) }
}
export type LensFn = {
<A, B>(getter: (a: A) => B, setter: (value: B, a: A) => A): Lens<A, B>
<A, B>(getter: (a: A) => B): (setter: (value: B, a: A) => A) => Lens<A, B>
}
Left-to-right composition of Lenses.
See the code
export const pipeLenses: PipeLenses = function pipeLenses<A, B>(
...lenses: Array<Lens<any, any>>
): Lens<A, B> {
return lenses.slice(1).reduce(__pipeLenses, lenses[0])
}
function __pipeLenses<A, B, C>(lensAB: Lens<A, B>, lensBC: Lens<B, C>): Lens<A, C> {
function view(obj: A): Maybe<C> {
return chain(b => lensBC.view(b), lensAB.view(obj))
}
function updateAt(f: (value: Maybe<C>) => Maybe<C>, obj: A): A {
const value = f(view(obj))
const nestedObject = lensAB.view(obj)
if (isNothing(nestedObject)) return obj
return lensAB.updateAt(
() => Maybe.of(lensBC.updateAt(() => value, fromJust(nestedObject))),
obj
)
}
return { view, updateAt: curry2(updateAt) }
}
Uses a lenses to update a value contained in an object.
See the code
export const updateAt: UpdateAt = curry3(function<A, B>(
lens: Lens<A, B>,
f: (value: Maybe<B>) => Maybe<B>,
obj: A
): A {
return lens.updateAt(f, obj)
})
export type UpdateAt = {
<A, B>(lens: Lens<A, B>, f: (value: Maybe<B>) => Maybe<B>, obj: A): A
<A, B>(lens: Lens<A, B>, f: (value: Maybe<B>) => Maybe<B>): (obj: A) => A
<A, B>(lens: Lens<A, B>): (f: (value: Maybe<B>) => Maybe<B>) => (obj: A) => A
<A, B>(lens: Lens<A, B>): (f: (value: Maybe<B>) => Maybe<B>, obj: A) => A
}
Uses a lenses to view a value contained in an object.
See the code
export const view: View = curry2(function<A, B>(lens: Lens<A, B>, obj: A): Maybe<B> {
return lens.view(obj)
})
export type View = {
<A, B>(lens: Lens<A, B>, obj: A): Maybe<B>
<A, B>(lens: Lens<A, B>): (obj: A) => Maybe<B>
}