How to design default implementations of type class methods?

I like to hear some opinions about how to implement class method defaults. Let's consider the following example: I want to classify types which allow for computation of the greatest common divisor. The greatest common divisor can be computed by Euclid's algorithm in some cases (integers, univariate polynomials) but not in all cases (integer matrices, multivariate polynomials). So maybe it's a good idea to set the Euclidean algorithm as default implementation for the GCD method. Say, I have already implemented both the normal Euclidean algorithm as "euclid", which only emits the GCD, and the extended Euclidean algorithm "extendedEuclid" which also determines a and b in "gcd(x,y) = a*x + b*y" for given x and y. Now I have two choices for implementing default methods: 1. Implement a method by calling other methods of the same class. class (Units a) => PID a where extendedGCD :: a -> a -> (a,a,a) extendedGCD = extendedEuclid divMod gcd :: a -> a -> a gcd x y = let (g,_,_) = extendedGCD x y in g Advantage: User must implement only few methods, here extendedGCD, the other ones work automatically. Disadvantage: User must know, how the default methods call each other, in order to not introduce cycles. 2. Implement a method by calling custom code, preferably a public function according to http://www.haskell.org/haskellwiki/Slim_instance_declaration class (Units a) => PID a where extendedGCD :: a -> a -> (a,a,a) extendedGCD = extendedEuclid divMod gcd :: a -> a -> a gcd = euclid mod Advantages/Disadvantages are negated with respect to the first item. :-) Ok, this example class is not well chosen, because in the case of multivariate polynomials, not only extended Euclidean algorithm fails, but it is in many cases not possible to represent the GCD of x and y as a linear combination of x and y. My question is not limited to this case: I want to know, what reasons exist pro and cons each alternative. Currently I prefer the first way, because it reduces the work of implementing instances to the things that are essential for the particular type. People who want specialized and more efficient methods, must implement all methods.

i haven't thought this through, but a variant of your first option may be to factor out extendedGCD into a class PIDEXT that is a parent of PID (occurs in the context of the declaration of PID). this way both methods will be available in PID. advantages: the dependencies are a little more explicit cycles are easier to avoid. drawbacks: more complicated interface less flexibility in implementation also i think there was an issue with superclass constraints not being inferrable from explicit subclass constraints, leading to bulky type signatures all over the code that is using your classes. but perhaps this gives somebody a better idea? m. On Mon, Nov 06, 2006 at 06:34:07PM +0100, Henning Thielemann wrote:
To: Haskell Cafe
From: Henning Thielemann Date: Mon, 06 Nov 2006 18:34:07 +0100 (CET) Subject: [Haskell-cafe] How to design default implementations of type class methods? I like to hear some opinions about how to implement class method defaults.
Let's consider the following example: I want to classify types which allow for computation of the greatest common divisor. The greatest common divisor can be computed by Euclid's algorithm in some cases (integers, univariate polynomials) but not in all cases (integer matrices, multivariate polynomials). So maybe it's a good idea to set the Euclidean algorithm as default implementation for the GCD method. Say, I have already implemented both the normal Euclidean algorithm as "euclid", which only emits the GCD, and the extended Euclidean algorithm "extendedEuclid" which also determines a and b in "gcd(x,y) = a*x + b*y" for given x and y.
Now I have two choices for implementing default methods:
1. Implement a method by calling other methods of the same class.
class (Units a) => PID a where extendedGCD :: a -> a -> (a,a,a) extendedGCD = extendedEuclid divMod
gcd :: a -> a -> a gcd x y = let (g,_,_) = extendedGCD x y in g
Advantage: User must implement only few methods, here extendedGCD, the other ones work automatically. Disadvantage: User must know, how the default methods call each other, in order to not introduce cycles.
2. Implement a method by calling custom code, preferably a public function according to http://www.haskell.org/haskellwiki/Slim_instance_declaration
class (Units a) => PID a where extendedGCD :: a -> a -> (a,a,a) extendedGCD = extendedEuclid divMod
gcd :: a -> a -> a gcd = euclid mod
Advantages/Disadvantages are negated with respect to the first item. :-)
Ok, this example class is not well chosen, because in the case of multivariate polynomials, not only extended Euclidean algorithm fails, but it is in many cases not possible to represent the GCD of x and y as a linear combination of x and y.
My question is not limited to this case: I want to know, what reasons exist pro and cons each alternative. Currently I prefer the first way, because it reduces the work of implementing instances to the things that are essential for the particular type. People who want specialized and more efficient methods, must implement all methods. _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- Institute of Information Systems, Humboldt-Universitaet zu Berlin web: http://www.wiwi.hu-berlin.de/~fis/ e-mail: fis@wiwi.hu-berlin.de tel: +49 30 2093-5742 fax: +49 30 2093-5741 office: Spandauer Strasse 1, R.324, 10178 Berlin, Germany pgp: AD67 CF64 7BB4 3B9A 6F25 0996 4D73 F1FD 8D32 9BAA

G'day all.
Quoting Henning Thielemann
I like to hear some opinions about how to implement class method defaults.
In this case, don't. Use instance defaults instead. class (Eq a) => Ring a where (*),(+),(-) :: a -> Integer zero, one :: a negate :: a -> a negate = (zero -) isZero :: a -> Bool isZero = (==zero) class (Ring a) => RingWithNorm a where nu :: a -> Integer class (RingWithNorm a) => EuclideanDomain a where divMod :: a -> a -> (a,a) class (RingWithNorm a) => GCD a where gcd :: a -> a -> a instance (EuclideanDomain a) => GCD a where gcd = {- Euclidean GCD algorithm; detail omitted -} Then if you have some other domain where GCD applies, you can create other instances as needed: class (RingWithNorm a) => SteinDomain a where smallestPrime :: a steinDecompose :: a -> Maybe (a,a) -- scale p q = p/q -- where nu q < nu smallestPrime scale :: a -> a -> a instance (SteinDomain a) => GCD a where gcd a b | nu a < nu b = gcd b a | a == b = a | otherwise = case (steinDecompose a, steinDecompose b) of (Nothing,_) -> b (_,Nothing) -> a (Just (pa,ca), Just (pb,cb)) | isZero ca && isZero cb -> smallestPrime * gcd pa pb | isZero cb -> gcd a pb | otherwise -> gcd (pa - scale (ca*pb) cb) b Cheers, Andrew Bromage
participants (3)
-
ajb@spamcop.net
-
Henning Thielemann
-
Matthias Fischmann