-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Please add 'f a -> f a -> f a' to signature for Divisible #28
Comments
This makes me wonder if Divisible is actually more like Alternative, and Decidable more like Applicative. Which wouldn't matter much except that Divisible is now a superclass of Decidable, and maybe that should be the other way around? |
Let's look at how these classes treat products:
From this angle |
Yes, but we're going from covariance to contravariance, so I would actually expect that product and coproduct switch roles. |
I don't have this paged in to my working memory well enough to refute that categorically, but I can argue that pragmatically, the hierarchy running the other way is rather less useful. Far more things can handle 'lots of products and fixed shape' than '1 choice out of a bunch of alternatives' that you might write with generics. In everything I've written using the origin of these abstractions the sum case is the thing that needed something 'extra'. Sort of the difference between applicative and monad: The product case usually doesn't have to branch or choose what to do, just plumb stuff around and combine it. The sum case needs to make decisions. If we assume we're playing with (right)(semi)(near)rings and what have you the distributive laws also look like the usual direction, they don't flip either. So I think something is off in your intuition in this case. That said, you might argue that the sum case only has to make decisions it doesn't have to deal with combining anything. |
Best argument for switching them: |
That would be an example of the sum case that only has to make decisions and not combine. But then you can make the exact same argument about Alternative not needing Applicative as a superclass as there are plenty of functors for which a FunctorPlus would be available, but not Applicative. The problem then is that the operations you'd get from such a class don't have any natural distributive law in relation to the ones in Applicative (ignoring the fact that even Applicative/Alternative have one of 2-3 different sets of laws!). This would mean you'd really need three classes. A sum-based ContravariantPlus, a product based Divisible. and a Decidable that promoted the choose/lose to have at least the loose distributive laws that applicative/alternative have. I chose to forego the ContravariantPlus level of abstraction simply because of a lack of good usecases, and for symmetry with the other side. Similarly Comonad avoids introducing Semicomonad, because the asymmetry with Monad creates pain points for duality. |
You'd also need separate names for the Decidable combinators vs. the ContravariantPlus ones, for type inference reasons, lest you wind up with a bunch of code with lawless interactions between ContravariantPlus and Divisible, so restructuring this way isn't without a cost. |
To the original topic here, such a divide violates the "linear" intuition about how such predicates decompose. There is a real downside to it as well, consider using the variant form of divide proposed over and over recursively, to say divide out and handle separately everything element in a list (with the help of decidable for list length). You then have to contramap in the walk down the list at each step reverting this to the previous form with multiple passes or else you pay O(n^2) in the list length because each decision is doing its own I'm not terribly averse to adding a stylized |
I don't understand that. Could you explain? I've found it very natural in practice. Fair enough about the efficiency concerns though. I've never considered them. |
If you look at the way all the generic instances for these work they break apart the sums and products and do things with both sides. Not everything will behave that way obviously if you program with these combinators directly, but the intuition does help guide the api design. The efficiency concern was really the thing front and center in my mind when writing the code, though. |
divide (\x -> (x, x)) :: Divisible f => f a -> f a -> f a
is equivalent todivide :: (a -> (b, c)) -> f b -> f c -> f a
and surely more convenient. Could it be added to the typeclass signature?The text was updated successfully, but these errors were encountered: