7

Suppose that

class A a where
   m1 :: a -> a 
   m2 :: a -> a
   m3 :: a -> a
   ...

where it is possible to write default implementation for m2 and m3 by using m1.

Is it better to leave m2 and m3 into A or write them as extra functions m2 :: A a => a -> a outside A? Or in other words, is worth to minimize the class API or does it matter?

I checked (quickly) some style guides, like programmin guidelines and some of the links in large-scale design-question, and some books (craft & rwh), but couldn't find a recommendation to this one. If there are presentations, blogs or books covering this kind of issues, please, could you share some pointers?

Email-list thread type class design discusses this and might emphasize minimizing.

Community
  • 1
  • 1
Gspia
  • 809
  • 6
  • 15
  • 2
    Does your class have any laws regarding how `m2` and `m3` must behave? Consider `mconcat`, which has a default that can be overridden, but is expected to be equivalent to repeated application of `mappend`. – chepner Aug 12 '16 at 12:09
  • For that matter, is it possible to override the default if it is just an externally defined function? – chepner Aug 12 '16 at 12:14
  • In my case, yes they do: I'm playing with "parameterized equality" and the question in a way applies to Eq- and Ord-classes, too. I think it is convenient if you can choose, which method to write, when making instances. But how about min & max? – Gspia Aug 12 '16 at 12:19
  • @chepner, first thought to overriding externally defined function is to not import the function and write your own. – Gspia Aug 12 '16 at 12:23
  • If nothing else, keeping the function in the class provides documentation that an instance must support the function. Also, consider if someone were to define `class A a => B a`; does `B` require `m2` to be defined? – chepner Aug 12 '16 at 12:33
  • Good question. But I think it can be way more harmful to get this wrong if the functions can *not* be implemented in terms of each other. Consider *Monoid* vs. *Semigroup*, monadic *fail*, or the *pure*/*return* duplication that happened partly because a *Pointed* typeclass wasn't created way back when. Not that I blame anyone, but it's a tale worth thinking about. – MarLinn Aug 12 '16 at 13:17
  • @MarLinn: Do you think that I should reword the question to have two cases: one where functions can be implemented in terms of each other and one, where it is not possible? Your consern sound like one I'd like to be told more. – Gspia Aug 12 '16 at 13:45
  • @Gspia: I personally consider both questions to be completely different beasts. The one you asked is mostly about convenience and performance, while my variation is much more about underlying mathematical structures. So if at all I would ask a separate question. But honestly, I wouldn't expect many good answers because even or lib commitees seem to be struggling with that one. – MarLinn Aug 12 '16 at 14:11
  • One thing to consider: a class with no superclasses and exactly one method gets special treatment by GHC. If you expect that the class might not always specialize away, that can be a substantial optimization. – dfeuer Aug 12 '16 at 14:45

1 Answers1

9

Keep them in the class if

  • There are multiple different sets of minimal complete definitions for that class, and you can't a priori tell which is more simple/elegant for a given instance.
  • It can reasonably be expected that particular types (e.g. unboxed vectors) will allow more efficient implementations of m2 and m3 than the default one. If these aren't overridable as methods, you'd need to resort to rewrite rules for this kind of optimisation.

An example of a standard class with a great many “superfluous” methods is (in more recent versions) Foldable. This is in part due to historical reasons, but also because different containers can have very different stricness etc. properties, so the default implementations of some methods might be really bad for performance of some instances.

If neither of these considerations applies to your class, keep m2 and m3 out of the class. (You can always put them in later: if you provide a default implementation, it won't break anybody's code.)

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319