receta

Lens

12 exports from @ontologyio/receta/lens.

Everything exported from @ontologyio/receta/lens. New here? Start with the Lens guide.

Functions

compose

Composes two lenses to create a lens that focuses deeper into a structure.

Lens composition follows the mathematical composition pattern: compose(outer, inner) creates a lens that first applies outer, then inner.

This allows you to build complex lenses from simple ones.

Signature
function compose<S, A, B>(outer: Lens<S, A>, inner: Lens<A, B>): Lens<S, B>
Example
interface User {
  name: string
  address: Address
}

interface Address {
  street: string
  city: string
}

const addressLens = prop<User, 'address'>('address')
const cityLens = prop<Address, 'city'>('city')
const userCityLens = compose(addressLens, cityLens)

const user = {
  name: 'Alice',
  address: { street: '123 Main', city: 'Boston' }
}

view(userCityLens, user) // 'Boston'
set(userCityLens, 'NYC', user)
// => { name: 'Alice', address: { street: '123 Main', city: 'NYC' } }
Example
// Multiple composition
interface State {
  ui: { modal: { title: string } }
}

const uiLens = prop<State, 'ui'>('ui')
const modalLens = prop<State['ui'], 'modal'>('modal')
const titleLens = prop<State['ui']['modal'], 'title'>('title')

const modalTitleLens = compose(
  compose(uiLens, modalLens),
  titleLens
)
Example
// With array index: index works on readonly arrays and its focus may be undefined
interface Todo {
  text: string
  done: boolean
}

interface TodoList {
  todos: readonly Todo[]
}

const firstTodoLens = compose(prop<TodoList, 'todos'>('todos'), index<Todo>(0))

const list: TodoList = { todos: [{ text: 'Buy milk', done: false }] }

over(firstTodoLens, todo => todo && { ...todo, done: true }, list)
// => { todos: [{ text: 'Buy milk', done: true }] }

See also prop, path.

index

Creates a Lens focusing on a specific index in an array.

Allows immutable updates to array elements at a specific position. If the index is out of bounds, get returns undefined and set has no effect. Setting undefined has no effect either, so this lens can't remove an element.

Signature
function index<A>(idx: number): Lens<readonly A[], A | undefined>
Example
const firstLens = index<number>(0)
const thirdLens = index<number>(2)

const numbers = [1, 2, 3, 4, 5]

firstLens.get(numbers) // 1
thirdLens.get(numbers) // 3

firstLens.set(10)(numbers) // [10, 2, 3, 4, 5]
thirdLens.set(30)(numbers) // [1, 2, 30, 4, 5]
Example
// With objects in arrays
interface Todo {
  id: number
  text: string
  done: boolean
}

const todos: Todo[] = [
  { id: 1, text: 'Buy milk', done: false },
  { id: 2, text: 'Walk dog', done: true }
]

const secondTodoLens = index<Todo>(1)
secondTodoLens.get(todos) // { id: 2, text: 'Walk dog', done: true }

See also prop, path.

lens

Creates a Lens from a getter and setter function.

A lens is a composable abstraction for focusing on a specific part of a data structure. It consists of:

  • get: Extracts the focused value from the source
  • set: Returns a new source with the focused value updated
Signature
function lens<S, A>(get: (source: S) => A, set: (value: A) => (source: S) => S): Lens<S, A>
Example
interface User {
  name: string
  age: number
}

const nameLens = lens<User, string>(
  (user) => user.name,
  (name) => (user) => ({ ...user, name })
)

const user = { name: 'Alice', age: 30 }
const newName = nameLens.get(user) // 'Alice'
const updated = nameLens.set('Bob')(user) // { name: 'Bob', age: 30 }

See also prop, path.

optional

Creates a lens that handles potentially undefined values using Option.

This is useful when you have nullable fields and want to work with them safely using the Option pattern. The lens will return Some(value) if the value exists, or None if it's undefined/null.

Signature
function optional<S, A>(baseLens: Lens<S, A | undefined>): Lens<S, Option<A>>
Example
interface User {
  name: string
  email?: string
  phone?: string
}

const emailLens = prop<User, 'email'>('email')
const optionalEmailLens = optional(emailLens)

const user1 = { name: 'Alice', email: 'alice@example.com' }
const user2 = { name: 'Bob' }

view(optionalEmailLens, user1) // Some('alice@example.com')
view(optionalEmailLens, user2) // None
Example
// Setting through optional lens
import { some, none } from '@ontologyio/receta/option'

const user = { name: 'Alice' }

set(optionalEmailLens, some('alice@example.com'), user)
// => { name: 'Alice', email: 'alice@example.com' }

set(optionalEmailLens, none(), user)
// => { name: 'Alice', email: undefined }
Example
// Transforming optional values
import { map } from '@ontologyio/receta/option'

over(
  optionalEmailLens,
  emailOpt => map(emailOpt, email => email.toLowerCase()),
  user
)

See also prop, path.

over

Applies a transformation function to the value focused by a Lens.

This is the most powerful lens operation. It reads the current value, applies a transformation, and sets the result - all immutably.

Supports both data-first and data-last (for use in pipes).

Data-first
function over<S, A>(l: Lens<S, A>, fn: (a: A) => A, source: S): S
Data-last
function over<S, A>(l: Lens<S, A>, fn: (a: A) => A): (source: S) => S
Example
// Data-first
const ageLens = prop<User, 'age'>('age')
const user = { name: 'Alice', age: 30 }

over(ageLens, age => age + 1, user)
// => { name: 'Alice', age: 31 }
Example
// Data-last (in pipe)
import * as R from 'remeda'

const updated = R.pipe(
  user,
  over(prop<User, 'age'>('age'), age => age + 1)
) // { name: 'Alice', age: 31 }
Example
// String manipulation
const nameLens = prop<User, 'name'>('name')
const user = { name: 'alice', age: 30 }

over(nameLens, name => name.toUpperCase(), user)
// => { name: 'ALICE', age: 30 }
Example
// Complex transformations with nested lenses
interface State {
  cart: {
    items: Array<{ id: string; quantity: number }>
  }
}

const itemsLens = path<State, any>('cart.items')

over(
  itemsLens,
  items => items.map(item =>
    item.id === 'abc' ? { ...item, quantity: item.quantity + 1 } : item
  ),
  state
)
Example
// Incrementing a counter (React state pattern)
const countLens = prop<State, 'count'>('count')
setState(over(countLens, n => n + 1))

See also view, set, prop.

path

Creates a Lens focusing on a nested property using dot notation.

Allows deep access into nested objects using string paths like 'user.address.city'. The lens handles immutable updates at any depth.

Paths step through plain objects and arrays, so items.0.name reaches into the first item. The path string isn't checked against S, and the type A is taken on trust.

Signature
function path<S, A = unknown>(pathStr: string): Lens<S, A>
Example
interface User {
  name: string
  address: {
    street: string
    city: string
    zip: string
  }
}

const cityLens = path<User, string>('address.city')

const user = {
  name: 'Alice',
  address: { street: '123 Main St', city: 'Boston', zip: '02101' }
}

cityLens.get(user) // 'Boston'
cityLens.set('NYC')(user)
// => { name: 'Alice', address: { street: '123 Main St', city: 'NYC', zip: '02101' } }
Example
// Deeply nested access
interface State {
  ui: {
    modal: {
      isOpen: boolean
      data: { title: string }
    }
  }
}

const modalTitleLens = path<State, string>('ui.modal.data.title')

See also prop, lens.

prop

Creates a Lens focusing on a specific property of an object.

This is a convenience function for creating lenses that access a single property. It's type-safe and ensures the property exists on the object.

Signature
function prop<S, K extends keyof S>(key: K): Lens<S, S[K]>
Example
interface User {
  name: string
  email: string
  age: number
}

const nameLens = prop<User, 'name'>('name')
const emailLens = prop<User, 'email'>('email')

const user = { name: 'Alice', email: 'alice@example.com', age: 30 }

nameLens.get(user) // 'Alice'
nameLens.set('Bob')(user) // { name: 'Bob', email: 'alice@example.com', age: 30 }

See also lens, path, view, set.

set

Sets a new value through a Lens, returning an updated source object.

This creates a new object with the focused value updated, without mutating the original. The update is immutable and type-safe.

Supports both data-first and data-last (for use in pipes).

Data-first
function set<S, A>(l: Lens<S, A>, value: A, source: S): S
Data-last
function set<S, A>(l: Lens<S, A>, value: A): (source: S) => S
Example
// Data-first
const nameLens = prop<User, 'name'>('name')
const user = { name: 'Alice', age: 30 }

set(nameLens, 'Bob', user)
// => { name: 'Bob', age: 30 }
// Original user is unchanged
Example
// Data-last (in pipe)
import * as R from 'remeda'

const updated = R.pipe(
  user,
  set(prop<User, 'name'>('name'), 'Bob')
) // { name: 'Bob', age: 30 }
Example
// With nested lenses
const cityLens = path<User, string>('address.city')
const user = {
  name: 'Alice',
  address: { street: '123 Main', city: 'Boston', zip: '02101' }
}

set(cityLens, 'NYC', user)
// => {
//   name: 'Alice',
//   address: { street: '123 Main', city: 'NYC', zip: '02101' }
// }

See also view, over, prop.

view

Gets the value focused by a Lens from a source object.

This is the primary way to read through a lens. It applies the lens's get function to extract the focused value.

Supports both data-first and data-last (for use in pipes).

Data-first
function view<S, A>(l: Lens<S, A>, source: S): A
Data-last
function view<S, A>(l: Lens<S, A>): (source: S) => A
Example
// Data-first
const nameLens = prop<User, 'name'>('name')
const user = { name: 'Alice', age: 30 }
view(nameLens, user) // 'Alice'
Example
// Data-last (in pipe)
import * as R from 'remeda'

const result = R.pipe(
  user,
  view(prop<User, 'name'>('name'))
) // 'Alice'
Example
// With nested lenses
const cityLens = path<User, string>('address.city')
const user = {
  name: 'Alice',
  address: { street: '123 Main', city: 'Boston', zip: '02101' }
}

view(cityLens, user) // 'Boston'

See also set, over, prop.

Types

Lens

A Lens is a composable getter/setter pair for immutable updates.

A lens focuses on a specific part of a data structure, allowing you to read and update that part without mutating the original structure.

Signature
interface Lens<S, A> {
  readonly get: (source: S) => A

  readonly set: (value: A) => (source: S) => S
}
Example
// Lens focusing on a user's name
const nameLens: Lens<User, string> = {
  get: (user) => user.name,
  set: (name) => (user) => ({ ...user, name })
}

Path

A dot-separated path to a nested property, such as 'address.city'.

This is an alias for string: the path isn't checked against T.

Signature
type Path<T> = string
Example
const userPath: Path<User> = 'address.city'

PathValue

Type helper to extract the type at a given path.

Signature
type PathValue<T, P extends string> = P extends keyof T
  ? T[P]
  : P extends `${infer K}.${infer Rest}`
    ? K extends keyof T
      ? PathValue<T[K], Rest>
      : never
    : never

On this page