根据Learn You a Haskell:
使用 newtype 关键字代替 data 关键字。现在为什么是
那?一方面,newtype 更快。如果您使用 data 关键字
包装一个类型,所有包装和展开都有一些开销
当你的程序运行时。但是如果你使用 newtype,Haskell 知道
您只是使用它将现有类型包装成新类型
(因此得名),因为您希望它在内部相同但
有不同的类型。考虑到这一点,Haskell 可以摆脱
一旦它解析了哪个值是什么类型,就进行包装和解包。
那么为什么不一直使用 newtype 而不是 data 呢?好吧,
当您使用 newtype 从现有类型创建新类型时
关键字,你只能有一个值构造函数和那个值
构造函数只能有一个字段。但是有了数据,你就可以做数据
具有多个值构造函数的类型,每个构造函数都可以
有零个或多个字段:
data Profession = Fighter | Archer | Accountant
data Race = Human | Elf | Orc | Goblin
data PlayerCharacter = PlayerCharacter Race Profession
使用 newtype 时,您只能使用一个构造函数和一个
字段。
现在考虑以下类型:
data CoolBool = CoolBool { getCoolBool :: Bool }
这是您定义的普通代数数据类型
数据关键字。它有一个值构造函数,它有一个字段
其类型为 Bool。让我们创建一个模式匹配的函数
CoolBool 并返回值“hello”,无论 Bool 是否
CoolBool 里面是 True 还是 False:
helloMe :: CoolBool -> String
helloMe (CoolBool _) = "hello"
我们不要将此函数应用于普通的 CoolBool,而是将其扔给一个曲线球并将其应用于 undefined!
ghci> helloMe undefined
"*** Exception: Prelude.undefined
哎呀!一个例外!现在为什么会发生这个异常?定义的类型
使用 data 关键字可以有多个值构造函数(甚至
虽然 CoolBool 只有一个)。所以为了看看给定的值
为了我们的函数符合 (CoolBool _) 模式,Haskell 必须
评估该值足以查看使用了哪个值构造函数
当我们创造价值时。当我们尝试评估一个未定义的
值,哪怕一点点,都会抛出异常。
我们不使用CoolBool 的data 关键字,而是尝试使用
新类型:
newtype CoolBool = CoolBool { getCoolBool :: Bool }
我们不必
改变我们的 helloMe 函数,因为模式匹配语法是
如果您使用 newtype 或 data 来定义您的类型,则相同。让我们做
同样的事情,并将 helloMe 应用于未定义的值:
ghci> helloMe undefined
"hello"
成功了!嗯,这是为什么呢?好吧,就像我们说过的,当我们使用
newtype,Haskell 可以在内部表示新类型的值
以与原始值相同的方式。它不必添加另一个
在他们周围的盒子里,它只需要意识到价值是
不同种类。而且因为 Haskell 知道使用
newtype 关键字只能有一个构造函数,它不必
评估传递给函数的值以确保它
符合 (CoolBool _) 模式,因为 newtype 类型只能
有一个可能的值构造函数和一个字段!
这种行为差异可能看起来微不足道,但实际上很漂亮
很重要,因为它帮助我们意识到即使定义了类型
从程序员的角度来看,with data 和 newtype 的行为相似
view 因为它们都有值构造函数和字段,所以它们是
实际上是两种不同的机制。而数据可以用来制作
从头开始你自己的类型,newtype 用于制作一个全新的
从现有类型中键入。新类型值的模式匹配不是
就像从盒子里拿出东西(就像数据一样),更多的是
关于从一种类型到另一种类型的直接转换。
这是另一个来源。根据this Newtype article:
newtype 声明创建新类型的方式与创建数据的方式大致相同。
newtypes 的语法和用法几乎与
数据声明 - 实际上,您可以将 newtype 关键字替换为
数据,它仍然会编译,确实有很大的机会你的
程序仍然可以工作。然而,反之则不然——数据可以
仅当类型只有一个构造函数时才被替换为 newtype
里面只有一个字段。
一些例子:
newtype Fd = Fd CInt
-- data Fd = Fd CInt would also be valid
-- newtypes can have deriving clauses just like normal types
newtype Identity a = Identity a
deriving (Eq, Ord, Read, Show)
-- record syntax is still allowed, but only for one field
newtype State s a = State { runState :: s -> (s, a) }
-- this is *not* allowed:
-- newtype Pair a b = Pair { pairFst :: a, pairSnd :: b }
-- but this is:
data Pair a b = Pair { pairFst :: a, pairSnd :: b }
-- and so is this:
newtype Pair' a b = Pair' (a, b)
听起来很有限!那么为什么有人使用 newtype 呢?
简短版 一个构造函数和一个字段的限制
表示新类型和字段的类型是直接的
对应:
State :: (s -> (a, s)) -> State s a
runState :: State s a -> (s -> (a, s))
或者在数学术语中它们是同构的。这意味着之后
在编译时检查类型,在运行时这两种类型可以
处理基本相同,没有开销或间接
通常与数据构造函数相关联。所以如果你想声明
特定类型的不同类型类实例,或想要制作
一个类型抽象,你可以把它包装在一个新类型中,它会被考虑
与类型检查器不同,但在运行时相同。然后你可以
使用各种深层技巧,例如幻像或递归类型,而无需
担心 GHC 无缘无故地洗牌桶。
请参阅the article 了解混乱的部分...