我已经定义了一个名为的类型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)
在我的代码中,newtype
s采用Natural
as参数是很常见的.因为我希望这些类型成为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
本身实现Num
和Enum
类的类,然后对于每一个newtype
我只需要实现一些简单的函数(可能根本就没有任何函数)A
.但我不知道该怎么做,或者根本不可能.
有任何想法吗?
有一个名为GeneralizedNewtypeDeriving的扩展,您可以将其用于同一端.它允许您从基础类型"转移"定义到新类型.
这是一个小的,人为的代码示例:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Foo = Foo Integer deriving (Show, Eq, Num)\
Run Code Online (Sandbox Code Playgroud)
虽然它有点令人困惑:标准派生类喜欢Show
和Eq
仍将以正常方式派生.所以Foo
的Show
例子不同于Integer
.但是,所有其他类都直接进行,因此实例与Foo
s Num
实例相同Integer
.
你必须要小心一点,因为它并不总是适用于某些Haskell扩展.但是,对于简单的Num
情况,这是一个非常好的选择.我也相信即将推出的GHC版本正在解决一些常见问题GeneralizedNewtypeDeriving
,所以在不久的将来它应该成为一个更安全的扩展.