What are Prisms?

• A+
Category：Languages

I'm trying to achieve a deeper understanding of `lens` library, so I play around with the types it offers. I have already had some experience with lenses, and know how powerful and convenient they are. So I moved on to Prisms, and I'm a bit lost. It seems that prisms allow two things:

1. Determining if an entity belongs to a particular branch of a sum type, and if it does, capturing the underlying data in a tuple or a singleton.
2. Destructuring and reconstructing an entity, possibly modifying it in process.

The first point seems useful, but usually one doesn't need all the data from an entity, and `^?` with plain lenses allows getting `Nothing` if the field in question doesn't belong to the branch the entity represents, just like it does with prisms.

The second point... I don't know, might have uses?

So the question is: what can I do with a Prism that I can't with other optics?

Edit: thank you everyone for excellent answers and links for further reading! I wish I could accept them all.

Lenses characterise the has-a relationship; Prisms characterise the is-a relationship.

A `Lens s a` says "`s` has an `a`"; it has methods to get exactly one `a` from an `s` and to overwrite exactly one `a` in an `s`. A `Prism s a` says "`a` is an `s`"; it has methods to upcast an `a` to an `s` and to (attempt to) downcast an `s` to an `a`.

Putting that intuition into code gives you the familiar "get-set" (or "costate comonad coalgebra") variety of lenses,

``data Lens s a = Lens {     get :: s -> a,     set :: a -> s -> s } ``

and an "upcast-downcast" representation of prisms,

``data Prism s a = Prism {     up :: a -> s,     down :: s -> Maybe a } ``

`up` injects an `a` into `s` (without adding any information), and `down` tests whether the `s` is an `a`.

In `lens`, `up` is spelled `review` and `down` is `preview`. There’s no `Prism` constructor; you use the `prism'` smart constructor.

What can you do with a `Prism`? Inject and project sum types!

``_Left :: Prism (Either a b) a _Left = Prism {     up = Left,     down = either Just (const Nothing) } _Right :: Prism (Either a b) b _Right = Prism {     up = Right,     down = either (const Nothing) Just } ``

Lenses don't support this - you can't write a `Lens (Either a b) a` because you can't implement `get :: Either a b -> a`. As a practical matter, you can write a `Traversal (Either a b) a`, but that doesn't allow you to create an `Either a b` from an `a` - it'll only let you overwrite an `a` which is already there.

Aside: I think this subtle point about `Traversal`s is the source of your confusion about partial record fields.

`^?` with plain lenses allows getting `Nothing` if the field in question doesn't belong to the branch the entity represents

Using `^?` with a real `Lens` will never return `Nothing`, because a `Lens s a` identifies exactly one `a` inside an `s`. When confronted with a partial record field,

``data Wibble = Wobble { _wobble :: Int } | Wubble { _wubble :: Bool } ``

`makeLenses` will generate `Traversal`s, not `Lens`es.

``wobble :: Traversal' Wibble Int wubble :: Traversal' Wibble Bool ``

For an example of this upcast-downcast notion of `Prism`s in the real world, look to `Control.Exception.Lens`, which provides a collection of `Prism`s into Haskell's extensible `Exception` hierarchy. This lets you perform runtime type tests on `SomeException`s and inject specific exceptions into `SomeException`.

``_ArithException :: Prism' SomeException ArithException _AsyncException :: Prism' SomeException AsyncException -- etc. ``

(These are slightly simplified versions of the actual types. In reality these prisms are overloaded class methods.)

To summarise, `Lens`es and `Prism`s together encode the two core design tools of object-oriented programming, composition and subtyping. `Lens`es are a first-class version of Java's `.` and `=` operators, and `Prism`s are a first-class version of Java's `instanceof` and implicit upcasting.

One fruitful way of thinking about `Lens`es is that they give you a way of splitting up a composite `s` into a focused value `a` and some context `c`. Pseudocode:

``type Lens s a = exists c. s <-> (a, c) ``

In this framework, a `Prism` gives you a way to look at an `s` as being either an `a` or some context `c`.

``type Prism s a = exists c. s <-> Either a c ``

(I'll leave it to you to convince yourself that these are isomorphic to the simple representations I demonstrated above. Try implementing `get`/`set`/`up`/`down` for these types!)

In this sense a `Prism` is a co-`Lens`. `Either` is the categorical dual of `(,)`; `Prism` is the categorical dual of `Lens`.

You can also observe this duality in the "profunctor optics" framework - `Strong` and `Choice` are dual.

``type Lens  s t a b = forall p. Strong p => p a b -> p s t type Prism s t a b = forall p. Choice p => p a b -> p s t ``

This is more or less the representation which `lens` uses, because these `Lens`es and `Prism`s are very composable. You can compose `Prism`s to get bigger `Prism`s ("`a` is an `s`, which is a `p`") using `(.)`; composing a `Prism` with a `Lens` gives you a `Traversal`.