10

A prism is an optic for focusing into coproduct types, while affine traversal is a kind of optic which can focus at 0 of 1 element, i.e. AffineTraversal s t a b is isomorphic to (s -> Maybe a, (s, b) -> t). As far as I know, we get an affine traversal when a lens is composed with a prism, provided an appropriate encoding of prisms is used.

I am interested in moving the Maybe in that (naive) formulation to the setter side as opposed to the getter side, so that I can have an optic that always extracts exactly one element, but may fail to put it back.

My use case is related to refinement types. Imagine that we have a type A and its refinement B (B ⊆ A). Then there is a prism refined :: Prism' A B: an A may or may not be a valid B, but each B can be re get into an A. Combining a Lens' C A with refined, we have an affine traversal. In the other direction, one may imagine an optic unrefined which is a little bit smarter than re refined: an A can be turned into Just b, if it is a valid B, or Nothing, if it is not. Now if we combine a Lens' C B with unrefined, we have our dual affine traversal: it can always obtain A from C, but putting back any old A may violate C's invariant and yield Nothing instead of Just c. More complicated invariants may be ensured in a similar manner.

Interestingly, the monocle library for Scala offers prisms for refinement types, but nothing for the reverse direction.

I have hard times coming up with laws for these (s -> a, b -> Maybe t) and (s -> a, (s, b) -> Maybe t) gizmos, and I was wondering whether a more abstract formulation of optics is helpful.

I know that with profunctor lens, we have

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
type AffineTraversal s t a b = forall p. (Strong p, Choice p) => p a b -> p s t

which makes it really clear that a lens zooms into a product type, a prism zooms into a coproduct type, and an affine traversal has the ability to zoom into algebraic data type (products or coproducts, no less).

Is the answer connected to something like Cochoice or even Costrong (remove a product / coproduct from the profunctor instead of introducing it)? I wasn't able to restore the naive formulation from them, however...

Kristóf Marussy
  • 1,202
  • 8
  • 18
  • 2
    I probably haven't fully understood your question, but I believe the dual of `Strong` is `Choice` and correspondingly the dual of `Lens` is `Prism`. ([Source](https://hackage.haskell.org/package/lens-4.15.4/docs/Control-Lens-Prism.html#t:Prism).) That makes the dual of `AffineTraversal`... `AffineTraversal`. – Benjamin Hodgson Mar 20 '18 at 00:58
  • "dual" was probably an unfortunate choice of wording from ne, then – Kristóf Marussy Mar 20 '18 at 01:00
  • You can "turn optics around" with `Re` to turn `Prisms a b s t` into the profunctor optic equivalent of `(s -> a, b -> Maybe t)`, see this blogpost http://oleg.fi/gists/posts/2017-04-18-glassery.html – Li-yao Xia Mar 20 '18 at 01:00

1 Answers1

4

Here's half an answer, showing the correspondence between the Cochoice optic and (s -> a, b -> Maybe t).

{-# LANGUAGE RankNTypes #-}

module P where

import Data.Profunctor
import Control.Monad

data P a b s t = P (s -> a) (b -> Maybe t)

instance Profunctor (P a b) where
  dimap f g (P u v) = P (u . f) (fmap g . v)

instance Cochoice (P a b) where
  unleft (P u v) = P (u . Left) (v >=> v') where
    v' (Left t) = Just t
    v' (Right _) = Nothing

type Coprism s t a b = forall p. Cochoice p => p a b -> p s t

type ACoprism s t a b = P a b a b -> P a b s t

fromCoprism :: ACoprism s t a b -> P a b s t
fromCoprism p = p (P id Just)

toCoprism :: P a a s t -> Coprism s t a a
toCoprism (P u v) = unleft . dimap f g where
  f (Left s) = u s
  f (Right a) = a
  g b = case v b of
    Nothing -> Right b
    Just t -> Left t
Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • It is somewhat unsatisfying that a type-chainging version of `toCoprism` is not possible. based on http://oleg.fi/gists/posts/2017-04-18-glassery.html , the solution is to define `P` in terms of `reviewE` instead of `review` by replacing `Maybe` with `Either a` – Kristóf Marussy Mar 20 '18 at 22:12
  • I also tried to implement concrete and profunctor representations for some interesting lenses: https://gist.github.com/kris7t/b29519bee925b070d657e5f7c6a5650a , although I haven't checked yet whether the `from*`/`to*` pairs are indeed isomorphism. It seems that the "partial setter" (`Foo` in my gist) is obtained by a `(Strong p, Cochoice p)` constraint – Kristóf Marussy Mar 20 '18 at 22:14