Decoupling type classes (e.g. Applicative)?

Hi all, I have a problem with the design of the Applicative type class, and I'm interested to know people's opinion about this. Currently, the Functor and Applicative type class are defined like this: class Functor f where fmap :: (a -> b) -> f a -> f b class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b My problem is that in the grammar-combinators library [1], the pure combinator is too general for me. I would propose a hierarchy like the following: class Pointed f where pure :: a -> f a class ApplicativeC f where (<*>) :: f (a -> b) -> f a -> f b The original type class Applicative can then be recovered as follows, and the applicative laws can be specified informally in this class's definition. class (Pointed f, ApplicativeC f, Functor f) => Applicative f where This would allow me to restrict injected values to stuff I can lift into Template Haskell later on: class LiftablyPointed f where pureL :: (Lift a) -> a -> f a pureL' :: a -> Q Exp -> f a class (LiftablyPointed f, ApplicativeC) => LiftablyApplicative f where This problem currently makes it impossible for me to use the (<*>) combinator and I have to redefine it under a different name (I currently use (>>>)). To me the problem seems similar to the well known example of the inclusion of the fail primitive in the monad class, where the general opinion seems to be that it was a bad idea to include fail in the Monad class (see e.g. the article on the haskell wiki about handling failure [2]). I've been thinking about the following type class design principles: * Only include two functions in the same design class if both can be implemented in terms of each other. * Only introduce a dependency from type class A to type class B if all functions in type class B can be implemented in terms of the functions in type class A or if type class A is empty. (Disclaimer: I currently do not follow these principles myself ;)) I would like to know people's opinions about this. Are there any issues with this advice that I don't see? Have other people encountered similar problems? Any interesting references? Thanks, Dominique Footnotes: [1] http://projects.haskell.org/grammar-combinators/ [2] http://www.haskell.org/haskellwiki/Failure

On 29 October 2010 14:35, Dominique Devriese < dominique.devriese@cs.kuleuven.be> wrote:
I have a problem with the design of the Applicative type class
Sorry for going a bit off-topic, but every-time I see someone complaining about such things, I remember this proposal: http://repetae.net/recent/out/classalias.html Just wanted to say, wouldn't it be nice? :) Best, Ozgur

I've been thinking about the following type class design principles:
* Only include two functions in the same design class if both can be implemented in terms of each other.
* Only introduce a dependency from type class A to type class B if all functions in type class B can be implemented in terms of the functions in type class A or if type class A is empty.
One of the other well-known type class inconveniences is the Num type class. The major inconvenience is that abs and signum are in the same type class as (+), (*), fromInteger etc, since within mathematics there are many structures on which the latter can be defined but not the former. The minor inconvenience is that (+) and (*) are in the same type class, since on occasion one wants an instance which defines one but not the other. I would also say that the Eq, Show constraints on Num are unwarranted. So to get to the point, I guess I would say that another design principle (perhaps a looser version of your first) is - don't put things together in a type class if they can be understood as separate concepts Unfortunately it is very hard, if not impossible, to get existing type classes changed, due to the amount of code that now depends on them. I have been thinking for a while that it might be worth defining a Prelude2, which corrects the known problems with the Prelude. Over time, people could migrate to using Prelude2. It would probably take years to be widely adopted, but at least there would be light at the end of the tunnel.

I have been thinking for a while that it might be worth defining a Prelude2, which corrects the known problems with the Prelude. Over time, people could migrate to using Prelude2. It would probably take years to be widely adopted, but at least there would be light at the end of the tunnel.
There are some (at least one) libraries that try to do that, for example: http://hackage.haskell.org/package/numeric-prelude Sönke

Sönke Hahn schrieb:
I have been thinking for a while that it might be worth defining a Prelude2, which corrects the known problems with the Prelude. Over time, people could migrate to using Prelude2. It would probably take years to be widely adopted, but at least there would be light at the end of the tunnel.
There are some (at least one) libraries that try to do that, for example: http://hackage.haskell.org/package/numeric-prelude
And there was also an effort for the rest of the Prelude: http://www.haskell.org/haskellwiki/The_Other_Prelude

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 10/29/10 09:35 , Dominique Devriese wrote:
* Only introduce a dependency from type class A to type class B if all functions in type class B can be implemented in terms of the functions in type class A or if type class A is empty.
Er? Eq a => Ord a makes perfect sense in context but violates this law. - -- brandon s. allbery [linux,solaris,freebsd,perl] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.10 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAkzQwZUACgkQIn7hlCsL25UXaACghD6I6JnoVZ3LTOsjy86ZWzmO hq4An06sQPiC2/Xr40xlTAA97xdhACud =nf0v -----END PGP SIGNATURE-----

On Tue, 2010-11-02 at 21:57 -0400, Brandon S Allbery KF8NH wrote:
On 10/29/10 09:35 , Dominique Devriese wrote:
* Only introduce a dependency from type class A to type class B if all functions in type class B can be implemented in terms of the functions in type class A or if type class A is empty.
Er? Eq a => Ord a makes perfect sense in context but violates this law.
x == y = case x `compare` y of EQ -> True; _ -> False Or using (==) inside definition: (==) = ((== EQ) .) . compare Class A - Ord, B - Eq. Regards

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 11/3/10 21:30 , Maciej Piechotka wrote:
On Tue, 2010-11-02 at 21:57 -0400, Brandon S Allbery KF8NH wrote:
On 10/29/10 09:35 , Dominique Devriese wrote:
* Only introduce a dependency from type class A to type class B if all functions in type class B can be implemented in terms of the functions in type class A or if type class A is empty.
Er? Eq a => Ord a makes perfect sense in context but violates this law.
x == y = case x `compare` y of EQ -> True; _ -> False
So, gratuitous duplication. Leading to the potential for the lovely case where Eq does one thing and `compare` does something else. This is an improvement? - -- brandon s. allbery [linux,solaris,freebsd,perl] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.10 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAkzYzTAACgkQIn7hlCsL25U91wCgz1kGXIyrlOqq69qnAyK4F1jm De0AoM8mwq39+qFQRRZSsf3Qu8dSLoQr =IMhV -----END PGP SIGNATURE-----
participants (7)
-
Brandon S Allbery KF8NH
-
DavidA
-
Dominique Devriese
-
Henning Thielemann
-
Maciej Piechotka
-
Ozgur Akgun
-
Sönke Hahn