Open-source project
ekmett/lens avatar
ekmett/lens

lens: composable optics for Haskell records and what the four type parameters mean

Lenses, Folds, and Traversals - Join us on web.libera.chat #haskell-lens

2,109 stars274 forksHaskellNOASSERTION

At a glance

What is it?
lens is a Haskell library on Hackage providing lenses, folds, traversals, isomorphisms, getters and setters as composable optics. Its Template Haskell macros derive typed optics from record field declarations at compile time.
Who is it for?
For Haskell programmers working with nested records, `makeLenses ''Foo` produces a typed optic for each field. .~ is an infix alias for set, (.) from the Prelude composes any 2 optics, and `Lens'` can be used directly as a `Lens` when the type does not change. Before adopting the library, run the README's first ghci session against a record type from your own codebase to check which generated optic names appear.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Haskell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Install from Hackage; the README opens with a ghci session as the first live use

lens is published on Hackage. A README badge links to hackage.haskell.org/package/lens, the installation source. Add lens to the Cabal file and open a REPL.

haskell
ghci> import Control.Lens

Reading the second element of a tuple:

haskell
ghci> ("hello","world")^._2
"world"

.~ is an infix alias for `set`. Writing to the first position:

haskell
ghci> _1 .~ "hello" $ ((),"world")
("hello","world")

Writes in this library can change the type of the container. In `set _2 42 ("hello","world")`, the second element in the result shifts from a string to an integer:

haskell
ghci> set _2 42 ("hello","world")
("hello",42)

Both before and after types sit in the optic itself. `view` is a prefix alias for `^.`. `(%~)` is an infix alias for `over`. Applying `over (mapped._2) succ [(1,2),(3,4)]` returns `[(1,3),(3,5)]`, applying `succ` to the second element of each pair. Reading and writing to tuple positions are the first operations shown, using nested tuples as the primary running examples throughout the opening session.

Lens carries 4 type parameters because a write can change the type of the whole

Applying `makeLenses` to a record:

haskell
data Foo a = Foo { _bar :: Int, _baz :: Int, _quux :: a }
makeLenses ''Foo

generates:

haskell
bar, baz :: Lens' (Foo a) Int
quux :: Lens (Foo a) (Foo b) a b

One sentence from the README explains the split: "A `Lens` takes 4 parameters because it can change the types of the whole when you change the type of the part." Often "you won't need this flexibility, a `Lens'` takes 2 parameters, and can be used directly as a `Lens`."

Each field name in the record has a leading prefix. Each generated optic name has that prefix removed. Field `_bar` produces optic bar, `_baz` produces baz, and `_quux` produces `quux`. `makePrisms` covers sum types and newtypes by the same notation, as the `makePrisms ''Neither` call in the next section shows.

Composition joins two optics with (.) from the Prelude and reads left to right

Composition in this library "just uses `(.)` from the `Prelude`". Two lenses chain with a period, and the leftmost applies first:

haskell
ghci> ("hello",("world","!!!"))^._2._1
"world"

For extending a chain with a pure function: "You can make a `Getter` out of a pure function with `to`."

haskell
ghci> ("hello",("world","!!!"))^._2._2.to length
3

That result is 3 because `"!!!"` has 3 characters. Writing to a nested position uses the same chained form:

haskell
ghci> set (_2._1) 42 ("hello",("world","!!!"))
("hello",(42,"!!!"))

`both *~ 2` multiplies each element of a pair:

haskell
ghci> both *~ 2 $ (1,2)
(2,4)

Applying `_1.mapped._2.mapped %~ succ` to a nested structure:

haskell
ghci> _1.mapped._2.mapped %~ succ $ ([(42, "hello")],"world")
([(42, "ifmmp")],"world")

Four optics connect by (.) in that single chain, reaching the characters of the string inside a list inside the first element of the outer pair.

makePrisms and makeLenses both derive an isomorphism from Neither, in opposite directions

For a newtype over `Either`:

haskell
newtype Neither a b = Neither { _nor :: Either a b } deriving (Show)
makePrisms ''Neither

generates:

haskell
_Neither :: Iso (Neither a b) (Neither c d) (Either a b) (Either c d)

Applying `makeLenses` to the same declaration:

haskell
makeLenses ''Neither

generates:

haskell
nor :: Iso (Either a b) (Either c d) (Neither a b) (Neither c d)

The type constructor in the declaration is Neither. The generated optic from makePrisms is `_Neither`: Neither with a leading prefix added. The record field in the declaration is `_nor`; makeLenses uses it to produce nor, dropping the leading prefix from the field name. The type constructor Neither produces `_Neither`; the field `_nor` produces nor. Both macros start from the same data declaration and produce isomorphisms, but at different type positions. Comparing the 2 signatures reveals what the README's following claim skips over.

The Neither and nor isomorphisms have their first type position reversed

Both signatures from README.markdown side by side:

haskell
_Neither :: Iso (Neither a b) (Neither c d) (Either a b) (Either c d)
nor      :: Iso (Either a b)  (Either c d)  (Neither a b) (Neither c d)

In _Neither, the first type is Neither a b; in nor, the first type is Either a b. Their positions are swapped. The README then states that nor "behaves identically to `_Neither`." That claim describes what both isomorphisms can do: reading and writing in either direction through `from`. The README demonstrates the round-trip: `_Neither.from _Neither = id` and `from _Neither._Neither = id`.

The README also shows `packed` as an isomorphism between String and Text, and gives this example of `from` reversing direction in a chain:

haskell
ghci> let hello = "hello"^.packed
ghci> hello^.from packed.to length
5

A chain starting from a Neither value uses _Neither directly; a chain starting from an Either value uses nor directly. What makes the README's use of "identically" accurate is that isomorphisms are always invertible via `from`, regardless of which direction the type signature was written.

Folds and Traversals sit in composed optic chains alongside Lens

`mapMOf` applies monadic actions through a Traversal the way `mapM` applies them through a plain list. `allOf` checks predicates through a Fold. Both accept composed optic chains. An example with `mapMOf`:

haskell
ghci> mapMOf (traverse._2) (\xs -> length xs <$ putStrLn xs) [(42,"hello"),(56,"world")]
"hello"
"world"
[(42,5),(56,5)]

`traverse._2` composes by (.) with any other optic in the same chain.

Neil Mitchell's `uniplate` library is the credited origin for the `biplate` combinators, which the README says "have been generalized to work on or as lenses, folds, and traversals." `anyOf biplate` searches matching sub-terms in a nested structure:

haskell
ghci> anyOf biplate (=="world") ("hello",(),[(2::Int,"world")])
True

Using `uniplate` directly covers generic traversal alone, without (.) composition alongside other optic types.

That README body is truncated before completing a section on further features. A field guide at images/Hierarchy.png and the Lens Wiki cover what the README body does not reach.

State monad and arithmetic operators use the same optic types as read and write

Beyond static access, the library includes State monad combinators. A representative example from the README:

haskell
fresh :: MonadState Int m => m Int
fresh = id <+= 1

In that example, `id <+= 1` reads the current state value, returns it, and then increments the state by 1. There are a number of combinators that resemble the +=, *=, etc. operators from C/C++ for working with the monad transformers. There are +~, *~, etc. analogues to those combinators that work functionally, returning the modified version of the structure.

For a Fold across Text values, the README shows:

haskell
ghci> :m + Data.Char Data.Text.Lens
ghci> allOf (folded.text) isLower ["hello"^.packed, "goodbye"^.packed]
True

`folded.text` joins a fold over a list with a lens into a Text value, and `allOf` checks the predicate across every element reached by that chain. `packed` appears here as an isomorphism from Data.Text.Lens, the same lens used in the `from packed` example in the previous section.

The last push was on 2026-10-04 and the repository lists no GitHub releases. The licence is a custom text in the LICENSE file at the repository root.

Editorial conclusion

For Haskell programmers working with nested records, `makeLenses ''Foo` produces a typed optic for each field. .~ is an infix alias for set, (.) from the Prelude composes any 2 optics, and `Lens'` can be used directly as a `Lens` when the type does not change. Before adopting the library, run the README's first ghci session against a record type from your own codebase to check which generated optic names appear. For the full hierarchy, read the Lens Wiki FAQ and the derivation overview linked from the README, and check the field guide image at images/Hierarchy.png in the repository.

Frequently asked questions

How do I add the lens library to a Haskell project?

lens is published on Hackage at hackage.haskell.org/package/lens. Add it as a dependency in your Cabal file and use import Control.Lens to access the core API.

What is the difference between Lens and Lens' in the lens library?

A Lens takes 4 type parameters and covers writes that change the type of the container. Lens' takes 2 parameters for the case where the type does not change. Lens' can be used directly as a Lens.

What macros does the lens library provide for deriving optics?

makeLenses derives optics from record field names beginning with a leading prefix. makePrisms generates isomorphisms for sum types and newtypes. The README examples show both applied to concrete data types.

How do Fold and Traversal relate to Foldable and Traversable in this library?

A Fold covers inspection tasks the way Foldable does. A Traversal covers effectful updates the way traverse does. Both accept composed optic chains as their target rather than requiring a Foldable or Traversable instance directly.

What naming convention does makeLenses require in the lens library?

Field names must begin with a leading prefix. In the README example, fields _bar, _baz and _quux produce optics bar, baz and quux with the leading prefix removed from each generated name.

Official sources

  1. ekmett/lens on GitHub
  2. Issues
  3. Project website
  4. README