当我这样写代码时,有什么区别呢?
data Book = Book Int Int
对比
newtype Book = Book (Int, Int) -- "Book Int Int" is syntactically invalid
当我这样写代码时,有什么区别呢?
data Book = Book Int Int
对比
newtype Book = Book (Int, Int) -- "Book Int Int" is syntactically invalid
很好的问题!
有几个关键区别。
表示法
newtype
保证您的数据在运行时将具有与包装的类型完全相同的表示形式。data
声明了一个全新的数据结构在运行时。因此,这里的关键点是 newtype
的结构在编译时被保证被擦除。
示例:
data Book = Book Int Int
newtype Book = Book (Int, Int)
(Int,Int)
具有完全相同的表示方式,因为 Book
构造函数被擦除。
data Book = Book (Int, Int)
这里有一个额外的Book
构造函数,而在newtype
中并不存在。
data Book = Book {-# UNPACK #-}!Int {-# UNPACK #-}!Int
没有指针!在Book
构造函数中,这两个Int
字段是未装箱的字长字段。
代数数据类型
由于需要抹除构造函数,因此newtype
仅在包装具有单个构造函数的数据类型时起作用。没有“代数”newtype
的概念。也就是说,你不能编写一个等同于以下内容的newtype
:
data Maybe a = Nothing
| Just a
因为它有多个构造函数,所以你不能编写。也不能编写解释。
newtype Book = Book Int Int
严格性
构造函数被擦除的事实导致data
和newtype
之间存在一些非常微妙的严格性差异。特别是,data
引入了一个“提升”的类型,这意味着它有一种额外的方式可以评估为底部值。由于在运行时没有额外的构造函数使用newtype
,因此不具备此属性。
Book
中指向(,)
构造函数的额外指针使我们可以放置一个底部值。
因此,newtype
和data
具有略微不同的严格性属性,如在Haskell维基文章中所述。
拆箱
对于newtype
的组件进行拆箱是没有意义的,因为没有构造函数。虽然编写以下代码是完全合理的:
data T = T {-# UNPACK #-}!Int
T
构造器和 Int#
组件的运行时对象。你只需使用 newtype
获取裸的 Int
。
参考资料:
newtype
在编译后被擦除,并且运行时使用相同的表示来表示旧类型和新类型,我们如何仍然能够为旧类型和新类型定义实例?运行时如何理解要使用哪个实例? - Konstantin Milyutinnewtype
显然��没有被擦除。 - semicolon它们在语义上有所不同。
data
定义了一个 GADT(积类型、和类型等)newtype
定义了一个同构。当你不关心它是否同构时,应该使用 data
,即使它只有一个字段。
例如,
data Student = Student {
age :: Int
}
data
而不是 newtype
,因为你从来没有意思是一个学生应该同构于一个年龄。
newtype Book = Book Int Int
是无效的。然而,如dons所指出的那样,你可以使用newtype Book = Book (Int, Int)
。 - Edward KmettBook Int Int
相当语义无效,因为newtype
只能有一个值构造函数,且仅有一个字段。Book Int Int
有两个字段。 - LRDPRDX