views:

187

answers:

5

(Sorry in advance if the question is stupid or obvious -- I don't have a lot of experience with Haskell).

Is there a way to express that a type should be an instance of a typeclass in more than one way? This is best illustrated with an example (which is probably somewhat silly): In mathematics, we can say that a semiring is a set that is a commutative monoid under one operation (which we'll call addition, identity 0) and a monoid under another (which we'll call multiplication) along with the requirements that multiplication distributes over addition and that 0 annihilates all elements under multiplication. The latter parts aren't important here.

Suppose now that I have a typeclass Monoid (not to be confused with Data.Monoid),

class Monoid m where
    unit :: m 
    operation :: m -> m -> m

and would like to create a typeclass Semiring. From the definition given above, I'd like to say "if the type r is a monoid in two (distinct) ways, we'll call it semiring". So I'd like something like

class (Monoid r, Monoid r) => Semiring r where ...

which of course doesn't work. Admittedly, the example becomes a bit strange since there are no more functions we'd like to require for semirings, so the typeclass would be empty, but I hope it illustrates what I'm asking about (or just pretend that we require some function f:r->r for Semiring r).

So, in the general setting, I'm asking: Given a typeclass A, is there a way to parametrize a typeclass B a with the requirement that a be an instance of A in two ways (meaning that a should implement the functions specified by A in two ways)?

+5  A: 

For Monoid, specifically, this is done with type wrappers. If you look into the module Data.Monoid, you'll find two different monoidal structures for Bool values: Any and All, as well as two different structures for types that implement Num: Sum and Product and two structures for Maybe types: First and Last.

You'll run into problems with your semiring example, however, since the monoidal structures for Sum and Product both provide implementations of mempty (Haskell version of your unit) and mappend (Haskell version of your operation).

Mikael Vejdemo-Johansson
Hmm... thanks for the info, but it sounds like the answer boils down to "no", then? That's a shame :-(
gspr
+8  A: 

One option is to define your own monoids for the two operations of a semiring:

class AdditiveMonoid m where
    zero :: m
    (<+>) :: m -> m -> m

class MultiplicativeMonoid m where
    one :: m
    (<*>) :: m -> m -> m

and then combine them:

class (MultiplicativeMonoid m, AdditiveMonoid m) => Semiring m

The problem is that you cannot express the monoid laws or the fact that one operation is commutative. The best you can get is defining quickcheck properties for the laws.

For some inspiration check the numeric prelude and this paper.

Daniel Velkov
Thanks a lot! It seems the short answer is "no, you have to duplicate code", then? I'm aware of the numeric prelude, but it's so massive it can be hard to learn from when you're new. That paper seems very interesting though, thanks!
gspr
+3  A: 

See also Conor McBride's "rhythm section" post: http://www.haskell.org/pipermail/libraries/2008-January/008917.html, although that's at the value level and doesn't help with type classes.

Kmett's Monoids library (before he stripped out the Ring stuff) implemented something akin to Daniel Velkov's approach: http://hackage.haskell.org/package/monoids-0.1.36

I should add that the nice thing about that approach is that by defining additive and multiplicative on a data type distinctly, you can capture that they're not the same -- i.e., the latter distributes over the former.

sclv
+2  A: 

The common technique for this is, as other answers mention, newtype wrappers. In many cases this is seems to me something of an abuse of the type-class concept. Type classes are logical "axioms" that state that some fact is true of a type; for example, that Maybe is a Monad, or that Int is a Num, or that lists are ordered when their elements are. Often, as in the case of Eq and Ord, there are other reasonable definitions but the ones chosen are somehow 'canonical'. Other times, as in the case of Monoid, there is not.

In the case of Monoid and other highly abstract structures like it, I am of the opinion that a data declaration would be more useful. For example, data Monoid a = Monoid {mempty :: a ; mappend :: a -> a -> a}. Then, we have addition :: Num a => Monoid a, liftMaybe :: Monoid a -> Monoid (Maybe a), etc.

The same technique could be used to implement your Semiring. For example (using a Monoid data type as before): data Semiring a = Semiring { addition, multiplication :: Monoid a }.

mokus
In fact, if I understand things correctly, this is basically what GHC does under the hood to implement type class polymorphism. I'm not convinced of the benefit in user-level code, though.
Antal S-Z
+2  A: 

Other answers have mentioned newtype wrappers, but not given an explicit solution using them:

-- export these newtypes from the module defining Semiring
newtype Add a = Add a
newtype Multiply a = Multiply a

class (Monoid (Add a), Monoid (Multiply a)) => Semiring a where
  -- empty

instance Monoid (Add Integer) where
  unit = Add 0
  Add a `operation` Add b = Add (a + b)

-- etc.

You'll need some GHC extensions like FlexibleContexts.

Reid Barton
Thanks for the explicit code sample. It makes it all much clearer. Although, I'm sort of left with a feeling that I often get confused with when one should make typeclasses and when one should make types. In fact, to overcome the problem in question, I had made *typeclasses* Additive and Multiplicative myself. Your way seems nicer, but that means I have some wrong understanding of the relationship between what "should" be typeclasses and what "should" be types. Maybe I'll make another question :-)
gspr