ekmett / contravariant

Haskell 98 contravariant functors
http://hackage.haskell.org/package/contravariant
Other
73 stars 24 forks source link

Make Divisible unconquerable #49

Open treeowl opened 6 years ago

treeowl commented 6 years ago

It strikes me as quite strange and somewhat unfortunate that conquer is a method of Divisible. I really think it should be in a subclass. The fundamental idea of Divisible is it's for problems that can be handled in arbitrary pieces. The notion of a default handler looks like extra power on top of that. Here's a simple example:

newtype Flipparr a b = Fliparr {unfliparr :: b -> a}

instance Semigroup a => Divisible (Fliparr) a where
  divide f (Fliparr g) (Fliparr h) = Fliparr $ \a ->
    case f a of
      (b, c) -> g b <> h c

To get conquer, this needs a Monoid constraint.

So I think Divisible should drop conquer, and there should be a separate class for that.

endgame commented 5 years ago

This sounds like: "??? is to Divisible as Apply is to Applicative". It feels like it should be in semigroupoids, but isn't. Similarly: "??? is to Decideable as Alt is to Alternative". If we put them here instead of in semigroupoids we can set the superclass relationships up right, too. Once #57 is done we can also define analogous operators. Maybe these classes?

class Contravariant f => Divide f where
  divide :: (a -> (b, c)) -> f b -> f c -> f a

class Divide f => Divisible f where
  conquer :: f a

class Divide f => Decide f where
  choose :: (a -> Either b c) -> f b -> f c -> f a

class (Divisible f, Decide f) => Decideable f where
  lose :: (a -> Void) -> f a

I'm pattern-matching off the semigroupoids diagram, and skipping over Plus, because we don't seem to have an equivalent to MonadPlus.

It would be great if someone who knew the maths could check over this hierarchy and make sure that none of the splits result in lawless typeclasses.

treeowl commented 3 years ago

@ekmett, any thoughts?

ekmett commented 3 years ago

I'm actually exploring something similar in the hkd package, where it seems this notion of Semidivisible becomes way more common. I will admit I'm a bit uncomfortable with the explosion of names, instances, and granularity.

I went the other way with the Comonad class at one point, basically making Extend a superclass for a long time, but then anything that switched back and forth between Monad and Comonad failed to land on the nose, due to my inability to put in a matching superclass above Monad.

So my thoughts right now are that there is a tension between making a more broadly applicable class and making a class that people really can understand how to use and that matches up with what is available on the other side of the mirrored class hierarchy.

treeowl commented 3 years ago

I think my biggest objection to conquer always being there is that when programming by types, it can just go anywhere. The same, of course, applies to mempty, while pure (usually) doesn't do that.