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 Prism
s.
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:
b -> t
part is actually irrelevant here (you don't need it in order to construct a ProductPrism
)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?
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 ~ (->)
.