(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)?
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.
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
).
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With