I have defined a type called Natural
which is an positive integer that includes 0:
newtype Natural = Natural Integer
deriving (Eq, Ord)
instance Show Natural where
show (Natural i) = show i
toNatural :: (Integral i) => i -> Natural
toNatural x | x < 0 = error "Natural cannot be negative"
| otherwise = Natural $ toInteger x
fromNatural :: Natural -> Integer
fromNatural (Natural i) = i
instance Num Natural where
fromInteger = toNatural
x + y = toNatural (fromNatural x + fromNatural y)
x - y = let r = fromNatural x - fromNatural y in
if r < 0 then error "Subtraction yielded a negative value"
else toNatural r
x * y = toNatural (fromNatural x * fromNatural y)
abs x = x
signum x = toNatural $ signum $ fromNatural x
instance Enum Natural where
toEnum = toNatural . toInteger
fromEnum = fromInteger . fromNatural
In my code it is common with newtype
s that take a Natural
as parameter. And since I want these types to be instances of Num
and Enum
, I find myself reimplementing the same classes over and over:
newtype NodeId
= NodeId Natural
deriving (Show, Eq, Ord)
instance Num NodeId where
fromInteger = NodeId . toNatural
(NodeId x) + (NodeId y) = NodeId (x + y)
(NodeId x) - (NodeId y) = NodeId (x - y)
(NodeId x) * (NodeId y) = NodeId (x * y)
abs (NodeId x) = NodeId (abs x)
signum (NodeId x) = NodeId (signum x)
instance Enum NodeId where
toEnum = NodeId . toEnum
fromEnum (NodeId x) = fromEnum x
...
newtype InstructionId = InstructionId Natural
deriving (Show, Eq)
instance Num InstructionId where
fromInteger = InstructionId . toNatural
(InstructionId x) + (InstructionId y) = InstructionId (x + y)
(InstructionId x) - (InstructionId y) = InstructionId (x - y)
(InstructionId x) * (InstructionId y) = InstructionId (x * y)
abs (InstructionId x) = InstructionId (abs x)
signum (InstructionId x) = InstructionId (signum x)
instance Enum InstructionId where
toEnum = InstructionId . toEnum
fromEnum (InstructionId x) = fromEnum x
...
newtype PatternId = PatternId Natural
deriving (Show, Eq)
instance Num PatternId where
fromInteger = PatternId . toNatural
(PatternId x) + (PatternId y) = PatternId (x + y)
(PatternId x) - (PatternId y) = PatternId (x - y)
(PatternId x) * (PatternId y) = PatternId (x * y)
abs (PatternId x) = PatternId (abs x)
signum (PatternId x) = PatternId (signum x)
instance Enum PatternId where
toEnum = PatternId . toEnum
fromEnum (PatternId x) = fromEnum x
As you see these implementations are almost identical, which made me wonder whether I could implement some class A
that would itself implement the Num
and Enum
classes, and then for each newtype
I would only need to implement some simple function (maybe not any function at all) of A
. But I'm not sure how to do that or whether it's even possible at all.
Any ideas?