8
votes

Say I'm trying to define a new typeclass in Haskell, and among other things, it must have a + operation:

class Foo a where
    (+) :: a -> a -> a

(In practice the typeclass Foo would have more stuff, but let me stop here to keep this minimal.)

Now, I want to make the basic "Integer" type an instance of Foo; and as far as the + operation goes, I just want to keep the usual addition, which is already defined. How do I do that?

Needless to say, the following makes no sense:

instance Foo Integer where
    (+) x y = x+y

It loops forever when I ask Haskell to compute 2+3, but I suppose that's to be expected! I also tried putting nothing at all:

instance Foo Integer

This compiles, but then when I ask for 2+3 I get "No instance nor default method for class operation +". Again, it makes sense...

But how do we do that then?

I guess it's a general question "about the namespace", I mean, when two typeclasses use the same names, what happens? In my case, I get issues when trying to make a type (Integer) an instance of two typeclasses with such a name clash (Num and Foo).

From reading this question, I'm now afraid that what I'm asking for is just prohibited...

6
Right, I think you're barking up the wrong tree. As you mentioned -- you could ìmport qualified Prelude` and write every addition symbol as Prelude.+, but that seems...wildly unnecessary. Why does Foo have to use the + operator? If you simply change it to something unique, all this craziness goes away :)Adam Smith
@Adam: to take a very mathematical example (but i am, after all, a working mathematician...), you could imagine that Foo is really called AbelianGroup, so that the elements of a type of this class can be added, but NOT multiplied, so they don't belong to Num. Yet it is very natural to use + and not some other symbol... while elements of type "Integer" are certainly examples, among others.Pierre
PS you could rephrase my question as: how do you define a weaker version of Num, or of any typeclass, where some functions are not defined?Pierre
There is hackage.haskell.org/package/groups-0.4.1.0/docs/Data-Group.html, which builds groups and abelian groups on top of monoid, using the <> operator. Also- Num has no superclasses (it doesn't require Show or Eq)Samuel Barr
From figure 6 in haskell.org/onlinereport/basic.html#numbers I had deduced that Num required Eq and Show...?Pierre

6 Answers

9
votes

To give a specific solution, something like this would work without having to deal with clashing with (+):

class Foo a where
  (<+>) :: a -> a -> a

infixl 6 <+>

instance Foo Integer where
  (<+>) = (+)

Now your class Foo has its own operator, and the fixity declaration means that (<+>) will be parsed the same way as (+).

6
votes

If you really want to call your operation +, and you want to define it in terms of the + from Prelude, you can do it like this:

import Prelude (Integer, print)
import qualified Prelude ((+))

class Foo a where
  (+) :: a -> a -> a

instance Foo Integer where
  (+) x y = x Prelude.+ y

main = print (2 + 3 :: Integer)

This will output 5.

To prove that the definition of main is really using our new + operator and not the original one, we can change our definition of +:

import Prelude (Integer, print)
import qualified Prelude ((+))

class Foo a where
  (+) :: a -> a -> a

instance Foo Integer where
  (+) x y = x Prelude.+ y Prelude.+ 1

main = print (2 + 3 :: Integer)

This will output 6.

3
votes

I don't think you'd do this the same way in Haskell as in a pure math setting. You're trying to implement an Abelian group (it seems from your comments), which I understand (as someone who hasn't taken a mathematics course since High School) to be a group of type a where there exists a function f :: a -> a -> a such that f x y = f y x.

Contrast that with Haskells built-in (and oft-used) Monoid class and you'll see why I say Haskellers may approach this differently. Monoids are a group of type a where there exists a function f :: a -> a -> a such that f x (f y z) = f (f x y) z (and additionally, but unrelated, a value k such that f x k = x). It's representing associativity rather than commutativity, but is otherwise identical.

Monoids are represented as such:

class Monoid a where
  mempty :: a
  mappend :: a -> a -> a
  mconcat :: [a] -> a

and several are defined, like Sum

newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
  mempty = 0
  mappend (Sum x) (Sum y) = Sum (x+y)
  -- mconcat is defined as `foldr mappend mempty`

Note that it doesn't try to redefine (+) here. In fact it defines its own operator as a synonym for mappend: (<>). I recommend using a similar syntax with your Abelian group.

class Abelian a where
  (|<>|) :: a -> a -> a  -- or similar
2
votes

In Haskell, you can use your own Prelude instead of the standard one, and there are multiple existing alternative preludes. E.g. numeric-prelude defines (+) in Algebra.Additive.C.

2
votes

a programmer's approach   As others have said, it is probably best if you forfeit the convenient notation for additive and multiplicative monoids that is widely used in the mathematics community, and rather think of monoid in a more abstract fashion — as a distinct algebraic structure with a distinct operation that, generally, has nothing to do with addition and multiplication in number rings, of which the Num typeclass may be considered an approximate formalization. The Haskell type system is relying on you to make sure that same names refer to same things, and distinct names refer to distinct ones — this simple and logical rule helps it avoid confusion. This is why the monoidal operation of any monoid is usually referred to with a lozenge <> sign. (Actually, the operation on initial monoids is sometimes denoted ++, because of tradition.)

It is also somewhat illogical, but considered practical, that Num for usual numbers is defined first (and even on the hardware level), and the constituting monoids are extracted afterwards:

base-4.11.1.0:Data.Semigroup.Internal

...
198 instance Num a => Semigroup (Sum a) where                                                            
199         (<>) = coerce ((+) :: a -> a -> a)                                                           
...
227 instance Num a => Semigroup (Product a) where
228         (<>) = coerce ((*) :: a -> a -> a)
...

— So it happens that + gets occupied long before any abstract algebra comes into play.

 

an algebraist's approach   Nevertheless, it is very much possible to define a ring out of a type with two monoids:

{-# language FlexibleInstances #-}
{-# language FlexibleContexts #-}
{-# language UndecidableInstances #-}
{-# language TypeApplications #-}

module MonoidsField where

import Prelude (Integer, Semigroup, (<>), Monoid, mempty, Show, show)
import Data.Coerce

newtype Sum a = Sum { getSum :: a }

newtype Product a = Product { getProduct :: a }

data N = Z | S { predecessor :: N}

-- | Substract one; decrease.
dec :: Coercible a (N -> N) => a
dec = coerce predecessor

instance Show N
  where
    show Z = ""
    show x = '|' : show (predecessor x)

instance Semigroup (Sum N)
  where
    u <> (Sum Z) = u
    u <> v = coerce S (u <> dec v)

instance Monoid (Sum N)
  where
    mempty = Sum Z

instance Semigroup (Product N)
  where
    u <> (Product Z) = coerce (mempty @(Sum N))
    u <> v = let (*) =         (<>) @(Product N)
                 (+) = coerce ((<>) @(Sum N))
             in u + (u * dec v)

instance Monoid (Product N)
  where
    mempty = Product (S Z)

(+) :: Monoid (Sum a) => a -> a -> a
x + y = getSum (Sum x <> Sum y)

(*) :: Monoid (Product a) => a -> a -> a
x * y = getProduct (Product x <> Product y)

class PseudoRing a

instance (Monoid (Sum a), Monoid (Product a)) => PseudoRing a
  where
    -- You may add some functions that make use of distributivity between the additive and
    -- multiplicative monoid.

-- ^
-- λ (S (S (S Z))) + (S (S Z))
-- |||||
-- λ (S (S (S Z))) * (S (S Z))
-- ||||||

— As you see, the PseudoRing class does not add any operations by itself, but it does make sure that the two monoids it requires are defined. If you instantiate it, it is implied that you have ensured that the axiom of distribution holds.

As this example suggests, a subclass may be thought of as including in itself all the operations defined for its superclasses. So, it is a possibility that you proclaim instance Num a => Foo a and get to re-use the definition of +. You may then go as far as to define "partial" instances of Num for your own types that a priory will not have a definition for all the required methods. This approach is obviously unsafe, confusing and overall not advisable, but it may be just what you need, especially if decorated with an appropriate scientific license. So, your example becomes:

class Foo a

instance Num a => Foo a

 


Let me know if any of the above requires clarification!

1
votes

As others have answered, the easiest option is to pick a new name for the operator. This is the approach that most existing packages take, so that users of the library can continue using Num as usual.

As the OP notes in the comments, it is also possible to import Prelude qualified, and define + however you want. Any other modules that want to use your new + will need to do the same. They can either not import Prelude (via import Prelude () or NoImplicitPrelude), or import it qualified. This is pretty reasonable in a large codebase, where everyone knows the local convention and you can provide instances for all the types you use.