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

gab*_*lin 3 haskell class

我已经定义了一个名为的类型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
Run Code Online (Sandbox Code Playgroud)

在我的代码中,newtypes采用Naturalas参数是很常见的.因为我希望这些类型成为Num和的实例Enum,我发现自己一遍又一遍地重新实现相同的类:

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
Run Code Online (Sandbox Code Playgroud)

如你所见,这些实现几乎相同,这让我想知道我是否可以实现一些A本身实现NumEnum类的类,然后对于每一个newtype我只需要实现一些简单的函数(可能根本就没有任何函数)A.但我不知道该怎么做,或者根本不可能.

有任何想法吗?

Tik*_*vis 5

有一个名为GeneralizedNewtypeDeriving的扩展,您可以将其用于同一端.它允许您从基础类型"转移"定义到新类型.

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

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Foo = Foo Integer deriving (Show, Eq, Num)\
Run Code Online (Sandbox Code Playgroud)

虽然它有点令人困惑:标准派生类喜欢ShowEq仍将以正常方式派生.所以FooShow例子不同于Integer.但是,所有其他类都直接进行,因此实例与Foos Num实例相同Integer.

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