3

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 newtypes 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?

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
gablin
  • 4,678
  • 6
  • 33
  • 47
  • The type should be declared as `newtype Natural = Natural { unNatural :: Integer }`. (sorry, could not resist ;-) ) – Waldheinz Mar 24 '14 at 20:11

1 Answers1

6

There's an extension called GeneralizedNewtypeDeriving that you can use for the same end. It lets you "carry over" definitions from the underlying type to the newtype.

Here's a small, contrived code example:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Foo = Foo Integer deriving (Show, Eq, Num)\

It's a bit confusing though: standard derived classes like Show and Eq will still be derived the normal way. So Foo's Show instance is different from Integer's. However, all other classes get carried through directly, so Foo's Num instances is the same as Integer's.

You have to be a little careful with it because it does not always play well with certain Haskell extensions. However, for the simple Num case, it's a perfectly good option. I also believe that upcoming versions of GHC are fixing some of the common problems with GeneralizedNewtypeDeriving, so it should become a much safer extension in the near future.

Community
  • 1
  • 1
Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
  • Cool! This removed all that ugly duplicated code! =D – gablin Mar 24 '14 at 09:09
  • 1
    GHC 7.8 will have the Role system which fixes holes in GND. In particular, they go from being compiler bugs to library designer bugs—the library designer must choose whether or not their exposed types should be GND-applicable. – J. Abrahamson Mar 24 '14 at 14:52