16

Many types of optics have a van Laarhoven representation.

For example, a Lens of type Lens s t a b can be represented as:

 Functor f => (a -> f b) -> s -> f t

Similarly a Traversal, can be represented in a similar way, swapping the Functor constraint for Applicative:

 Applicative f => (a -> f b) -> s -> f t

Several optics frameworks, such as Monocle and Arrow define a type called Optional.

In Monocle's Optics heirarchy Optional fits between Lens and Traversal

As I understand it: If a Traversal is like a Lens that may have zero to many targets, then an Optional is like a Lens that may have zero to one targets.

In Monocle, Optional is defined as a pair of functions:

getOrModify :: s -> Either t a 
set :: (b, s) -> t

Comments in the Monocle source code suggest that it's also possible to represent an Optional "as a weaker PLens and weaker PPrism"

Is it possible to represent an Optional as a van Laarhoven function?

Joe
  • 1,479
  • 13
  • 22

1 Answers1

10

There would be a way to represent it if the Functor/Applicative/Monad hierarchy were more fine-grained. In particular:

class Functor f => Pointed f where
    pure :: a -> f a

type Optional s t a b = forall f. Pointed f => (a -> f b) -> s -> f t

Note that the type would probably be named Affine in the lens library if that was neatly in the class hierarchy.

Carl
  • 26,500
  • 4
  • 65
  • 86
  • 1
    This is conveniently completely fine for my use case, which is a "just for fun" Scala Optics Library where I've written the Functor Hierarchy from scratch, and can therefore do whatever I like with it. Am I right in thinking that this suggests that If I also had a another type-class between `Functor` and `Applicative` with `liftA2`, but no pure, then that would define an optic with _one to many_ targets? Does this have a name? – Joe Oct 27 '19 at 22:14
  • 1
    It does suggest that. I have no idea what the optic would be called. – Carl Oct 27 '19 at 22:32
  • 4
    @Joe That intermediate class is called `Apply` in the "semigroupoids" package. http://hackage.haskell.org/package/semigroupoids-5.3.3/docs/Data-Functor-Apply.html#t:Apply – danidiaz Oct 27 '19 at 22:35
  • 2
    ... and the corresponding optic in the `lens` package is called [`Traversal1`](http://hackage.haskell.org/package/lens-4.18.1/docs/Control-Lens-Traversal.html#t:Traversal1) – Benjamin Hodgson Oct 28 '19 at 18:34
  • @Carl How can we verify that this `type Optional s t a b` is indeed the correct representation for that optic, and that all the appropriate laws hold? This seems like a magical trick. Why was it `Pointed` and not, say, `Copointed`? For another given optic, how can we guess the correct profunctor representation? – winitzki Apr 16 '20 at 07:00
  • 2
    @winitzki If you write a bunch of van Laarhoven lenses and traversals by hand (very highly recommended - they're not magic), you eventually get a handle on how the operations correspond to the number of targets present in a data structure. `fmap` handles a structure with a single target. `pure` handles a structure with no targets. `(<*>)` combines substructures that have each handled targets. From this you can observe that something with only `fmap` and `pure` will let you handle 0 or 1 targets, but not more than 1. And that's `Pointed`. – Carl Apr 16 '20 at 15:58
  • As for `Profunctor` optics... Dunno. I've never really seen the appeal, so I haven't written enough of them by hand to see what the blocks are. My impression from the little I have done is that they don't seem as amenable to piecewise breakdowns. (That impression may be wrong.) – Carl Apr 16 '20 at 16:04