Generalization of strong and closed profunctors

442 views Asked by At

I was looking at the classes of strong and closed profunctors:

class Profunctor p where
    dimap :: (a' -> a) -> (b -> b') -> p a b -> p a' b'
class Profunctor p => Strong p where
    strong :: p a b -> p (c, a) (c, b)
class Profunctor p => Closed p where
    closed :: p a b -> p (c -> a) (c -> b)

((,) is a symmetric bifunctor, so it's equivalent to the definition in "profunctors" package.)

I note both (->) a and (,) a are endofunctors. It seems Strong and Closed have a similar form:

class (Functor f, Profunctor p) => C f p where
    c :: p a b -> p (f a) (f b)

Indeed, if we look at the laws, some also have a similar form:

strong . strong ≡ dimap unassoc assoc . strong
closed . closed ≡ dimap uncurry curry . closed

lmap (first f) . strong ≡ rmap (first f) . strong
lmap (. f)     . closed ≡ rmap (. f)     . closed

Are these both special cases of some general case?

2

There are 2 answers

1
Bartosz Milewski On BEST ANSWER

You could add Choice to the list. Both Strong and Choice (or cartesian and cocartesian, as Jeremy Gibbons calls them) are examples of Tambara modules. I talk about the general pattern that includes Closed in my blog post on profunctor optics (skip to the Discussion section), under the name Related.

0
leftaroundabout On

Very intriguing. This isn't really an answer, just thoughts...

So what we need is an abstraction over (,) and (->) which offers generalisation of assoc/curry and first/precompose. I'll address the former:

class Isotropic f where
  lefty :: f a (f b c) -> f (a,b) c
  righty :: f (a,b) c -> f a (f b c)
  -- lefty ≡ righty⁻¹

instance Isotropic (,) where
  lefty (a,(b,c)) = ((a,b),c)
  righty ((a,b),c) = (a,(b,c))

instance Isotropic (->) where
  lefty = uncurry
  righty = curry

Easy. Question is, are there any other instances of this? There's certainly the trivial one

newtype Biconst c a b = Biconst c

instance Isotropic (Biconst c) where
  lefty (Biconst c) = Biconst c
  righty (Biconst c) = Biconst c

Then the resulting profunctor

class Profunctor p => Stubborn p where
  stubborn :: p a b -> p (Biconst d c a) (Biconst d c b)

could as well be written

class Profunctor p => Stubborn p where
  stubborn :: p a b -> p d d

But the instances of this seem to come out rather too trivial as well, to be any use:

instance Stubborn (->) where
  stubborn _ = id
instance (Monad m) => Stubborn (Kleisli m) where
  stubborn (Kleisli _) = Kleisli pure
instance (Monoid m) => Stubborn (Forget m) where
  stubborn (Forget _) = Forget $ const mempty

I suspect that (,) and (->) really are the only useful cases for this, because they are the “free bifunctor” / “free profunctor”, respectively.