如何为新类型重用类实例?

时间:2014-03-24 07:59:29

标签: class haskell

我已经定义了一个名为Natural的类型,它是一个包含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

在我的代码中,newtypeNatural为参数很常见。由于我希望这些类型是NumEnum的实例,我发现自己一遍又一遍地重新实现相同的类:

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

如您所见,这些实现几乎相同,这让我想知道我是否可以实现一些本身实现ANum类的类Enum,然后为每个{ {1}}我只需要实现newtype的一些简单函数(可能根本不是任何函数)。但我不知道该怎么做,或者根本不可能。

有什么想法吗?

1 个答案:

答案 0 :(得分:5)

有一个名为GeneralizedNewtypeDeriving的扩展程序,您可以将其用于同一目的。它允许您从基础类型“结转”定义到新类型。

这是一个小的,人为的代码示例:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

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

但有点令人困惑:像ShowEq这样的标准派生类仍将以正常方式派生。因此Foo的{​​{1}}实例与Show的实例不同。但是,所有其他类都直接执行,因此Integer的{​​{1}}个实例与Foo的实例相同。

你必须要小心一点,因为does not always play well带有某些Haskell扩展。但是,对于简单的Num情况,这是一个非常好的选择。我也相信即将推出的GHC版本正在解决Integer的一些常见问题,所以在不久的将来它应该成为一个更安全的扩展。