Search code examples
haskellhaskell-lenslenses

What is the name of this prism-like optic, and is it useful for anything?


I'm currently working through tutorials for the Haskell lens package, so I can better understand the underlying mathematical foundations. And I was working through Prisms.

type Prism s t a b = forall p f. (Applicative f, Choice p) => p a (f b) -> p s (f t)

We find that a prism is effectively b -> t and s -> Either t a. We can exhibit this isomorphism directly.

-- Note: Stripping out the APrism / AReview type synonyms used
-- in Control.Lens for simplicity.

prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
prism bt seta = dimap seta (either pure (fmap bt)) . right'

matching :: Prism s t a b -> s -> Either t a
matching aprism s = let Market _ f = aprism (Market Identity Right) in
                    left runIdentity $ f s

review :: Prism s t a b -> b -> t
review areview = runIdentity . unTagged . areview . Tagged . Identity

(Note: Sources for Tagged and Market)

At this point, I had the thought "Hey, Choice is kind of just Strong but on Either rather than (,)". So I wanted to know what sort of prism-like thing you get if you replace Choice with Strong. I was expecting to write

type ProductPrism s t a b = forall p f. (Applicative f, Strong p) => p a (f b) -> p s (f t)

and find that ProductPrism s t a b is isomorphic to (b -> t, s -> (t, a)). But as I went to write these isomorphisms, I discovered two things:

  1. The b -> t part is actually irrelevant here (you don't need it in order to construct a ProductPrism)
  2. We don't need pure, so we can get by with Apply.

So I ended up with this.

type ProductPrism s t a b = forall p f. (Apply f, Strong p) => p a (f b) -> p s (f t)

-- Construct ProductPrism from (s -> (t, a))
productPrism :: (s -> (t, a)) -> ProductPrism s t a b
productPrism sta = dimap sta (\(t, fb) -> t <$ fb) . second'

-- Consume ProductPrism into (s -> (t, a))
split :: ProductPrism (Semi.First a) s t a -> s -> (t, a)
split pprism s = go $ pprism (\a -> (Semi.First a, a)) s
    where go (Semi.First a, t) = (t, a)

where Semi.First is the semigroup First (not the monoid version).

So ProductPrism s t a b, as I've defined it, is just s -> (t, a) with a comically unused b argument.

My question is: Is this type useful? Does this functional reference have a well-known name like Lens or Prism or Traversal? Does it give us any useful abstractions like many of the other optics do?


Solution

  • I think you've gotten the choice of the c parameter in Strong wrong. It shouldn't be c ~ t; it should be c ~ s. So, my read is that you're looking for the following:

    type PPrism s t a b = forall p f. (Functor f, Strong p) => p a (f b) -> p s (f t)
    
    pprism :: (s -> a) -> (b -> s -> t) -> PPrism s t a b
    pprism sa bst = dimap (\s -> (s, sa s)) (\(s, fb) -> flip bst s <$> fb) . second'
    
    unprism :: PPrism s t a b -> ((s -> a), (b -> s -> t))
    unprism pp = (sa, bst)
      where sa = getConst . pp (Const . id)
            bst b = runIdentity . pp (const (Identity b))
    

    In short, this is really just a lens. (Or, if you choose Applicative f instead, a traversal.)

    That makes sense, right? A Prism is an optical generalization of a sum type based on a profunctor with Choice. A Lens is an optical generalization of a product type based on a profunctor with Strong, though we usually just define it with p ~ (->).