Skip to content

Latest commit

 

History

History

lenses

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

@typed/lenses -- 3.0.0

Well-typed functional lenses

Get it

yarn add @typed/lenses
# or
npm install --save @typed/lenses

API Documentation

All functions are curried!

Lens

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
}

composeLenses(...lens: Array<Lens<any, any>): Lens<any, any>

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())
}

lens<A, B>(getter: (a: A) => B | void, setter: (value: B, a: A) => A): Lens<A, B>

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>
}

pipeLenses<A, B>(...lenses: Array<Lens<any, any>>): 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) }
}

updateAt<A, B>(lens: <A, B>, f: (value: Maybe<B>) => Maybe<B>, obj: A): A

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
}

view<A, B>(lens: Lens<A, B>, obj: A): Maybe<B>

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>
}