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!
Prelude.+
, but that seems...wildly unnecessary. Why doesFoo
have to use the+
operator? If you simply change it to something unique, all this craziness goes away :) – Adam Smith