Introduction to optics: lenses and prisms

The problem

This tutorial assumes that our data structures are immutable. To see why we might want to consider optics, we’ll look at a simple example. We’ll define two object types

type Street = { num: number, name: string };
type Address = { city: string, street: Street };
const a1: Address = 
{ city: 'london', street: { num: 23, name: 'high street' } }
const name =
const a2: Address = {
street: {
name: 'main street'


A lens is a first-class reference to a subpart of some data type. Given a lens there are essentially three things you might want to do

  • view the subpart
  • modify the whole by changing the subpart
  • combine this lens with another lens to look even deeper
interface Lens<S, A> {
get(s: S): A,
set(a: A, s: S): S
const address: Lens<Address, Street> = {
get: address => address.street,
set: (street, address) => ({ ...address, street })
// => {num: 23, name: "high street"}
address.set({num: 23, name: 'main street'}, a1)
// => {city: "london", street: {num: 23, name: "main street"}}
const street: Lens<Street, string> = {
get: street =>,
set: (name, street) => ({ ...street, name })


The great thing about lenses is that they compose

function composeLens<A, B, C>(ab: Lens<A, B>, bc: Lens<B, C>): Lens<A, C> {

return {
get: a => bc.get(ab.get(a)),
set: (c, a) => ab.set(bc.set(c, ab.get(a)), a)
const streetName = composeLens(address, street)streetName.get(a1)
// => "high street"
streetName.set('main street', a1)
// => {city: "london", street: {num: 23, name: "main street"}}


Let’s say we need to set the first character of the address street name in upper case. Mapping a function over a part of a data structure given a lens is as simple as get the value, apply the function to the value, set the new value to be the result

function overLens<S, A>(lens: Lens<S, A>, f: (a: A) => A, s: S): S {
return lens.set(f(lens.get(s)), s)
function capitalize(s: string): string {
return s.substring(0, 1).toUpperCase() + s.substring(1)
overLens(streetName, capitalize, a1)
// => {city: "london", street: {num: 23, name: "High street"}}


In the above example, we used capitalize to upper case the first letter of a string. It works but it would be clearer if we could use Lens to zoom into the first character of a string. However, we cannot write such a Lens because a Lens defines how to focus from an object S into a mandatory object A and in our case, the first character of a string is optional as a string might be empty. For this we need a sort of partial Lens.

interface Prism<S, A> {
get(s: S): ?A,
set(a: A, s: S): S
const first: Prism<string, string> = {
get: s => s ? s.substring(0, 1) : null,
set: (a, s) => s.length ? a + s.substring(1) : ''
function composePrism<A, B, C>(ab: Prism<A, B>, bc: Prism<B, C>): Prism<A, C> {  return {
get: a => {
const b = ab.get(a)
return b == null ? null : bc.get(b)
set: (c, a) => {
const b = ab.get(a)
return b == null ? a : ab.set(bc.set(c, b), a)
function overPrism<S, A>
(prism: Prism<S, A>, f: (a: A) => A, s: S): S {
const a = prism.get(s)
return a ? prism.set(f(a), s) : s
function toUpper(s: string): string {
return s.toUpperCase()
overPrism(composePrism(streetName, first), toUpper, a1)
// => {city: "london", street: {num: 23, name: "High street"}}

Union types

Prisms are applicable to all union types. How would we write a prism for a custom union type of our own?

type Domicile
= { type: 'office', address: Address }
| { type: 'personal', address: string };
const office: Prism<Domicile, Address> = {
get: d => d.type === 'office' ? d.address : null,
set: (address, d) => d.type === 'office' ?
{ type: 'office', address } : d
const d1 = { type: 'office', address: a1 }
const d2 = { type: 'personal', address: '23 high street' }
// => {city: "london", street: {num: 23, name: "high street"}}
// => null



mathematician and rock climber

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


mathematician and rock climber