我已经定义了一个名为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
在我的代码中,newtype
以Natural
为参数很常见。由于我希望这些类型是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
如您所见,这些实现几乎相同,这让我想知道我是否可以实现一些本身实现A
和Num
类的类Enum
,然后为每个{ {1}}我只需要实现newtype
的一些简单函数(可能根本不是任何函数)。但我不知道该怎么做,或者根本不可能。
有什么想法吗?
答案 0 :(得分:5)
有一个名为GeneralizedNewtypeDeriving的扩展程序,您可以将其用于同一目的。它允许您从基础类型“结转”定义到新类型。
这是一个小的,人为的代码示例:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Foo = Foo Integer deriving (Show, Eq, Num)\
但有点令人困惑:像Show
和Eq
这样的标准派生类仍将以正常方式派生。因此Foo
的{{1}}实例与Show
的实例不同。但是,所有其他类都直接执行,因此Integer
的{{1}}个实例与Foo
的实例相同。
你必须要小心一点,因为does not always play well带有某些Haskell扩展。但是,对于简单的Num
情况,这是一个非常好的选择。我也相信即将推出的GHC版本正在解决Integer
的一些常见问题,所以在不久的将来它应该成为一个更安全的扩展。