Haskell中`data`和`newtype`的区别

232

当我这样写代码时,有什么区别呢?

data Book = Book Int Int

对比

newtype Book = Book (Int, Int) -- "Book Int Int" is syntactically invalid

与https://dev59.com/V3E85IYBdhLWcg3wtV1T相关的内容 - Don Stewart
相关内容:newtype 的用途:https://dev59.com/93NA5IYBdhLWcg3wWsiK - Don Stewart
28
请注意,newtype Book = Book Int Int 是无效的。然而,如dons所指出的那样,你可以使用 newtype Book = Book (Int, Int) - Edward Kmett
除了@EdwardKMETT的评论外,我认为Book Int Int相当语义无效,因为newtype只能有一个值构造函数,且仅有一个字段。 Book Int Int有两个字段。 - LRDPRDX
2个回答

291

很好的问题!

有几个关键区别。

表示法

  • newtype 保证您的数据在运行时将具有与包装的类型完全相同的表示形式。
  • data 声明了一个全新的数据结构在运行时。

因此,这里的关键点是 newtype 的结构在编译时被保证被擦除。

示例:

  • data Book = Book Int Int

data

  • newtype Book = Book (Int, Int)

newtype

请注意,它与 (Int,Int) 具有完全相同的表示方式,因为 Book 构造函数被擦除。
  • data Book = Book (Int, Int)

data tuple

这里有一个额外的Book构造函数,而在newtype中并不存在。

  • data Book = Book {-# UNPACK #-}!Int {-# UNPACK #-}!Int

enter image description here

没有指针!在Book构造函数中,这两个Int字段是未装箱的字长字段。

代数数据类型

由于需要抹除构造函数,因此newtype仅在包装具有单个构造函数的数据类型时起作用。没有“代数”newtype的概念。也就是说,你不能编写一个等同于以下内容的newtype

data Maybe a = Nothing
             | Just a

因为它有多个构造函数,所以你不能编写。也不能编写解释。

newtype Book = Book Int Int

严格性

构造函数被擦除的事实导致datanewtype之间存在一些非常微妙的严格性差异。特别是,data引入了一个“提升”的类型,这意味着它有一种额外的方式可以评估为底部值。由于在运行时没有额外的构造函数使用newtype,因此不具备此属性。

Book中指向(,)构造函数的额外指针使我们可以放置一个底部值。

因此,newtypedata具有略微不同的严格性属性,如在Haskell维基文章中所述

拆箱

对于newtype的组件进行拆箱是没有意义的,因为没有构造函数。虽然编写以下代码是完全合理的:

data T = T {-# UNPACK #-}!Int

产生一个带有 T 构造器和 Int# 组件的运行时对象。你只需使用 newtype 获取裸的 Int

参考资料:


3
如果 Haskell 没有 "newtype",我仍然不认为我会错过什么。这种微妙的差异增加了语言的复杂性,但对我来说似乎不值得。 - martingw
20
使用newtype有助于提高性能。由于newtype构造函数在编译时被擦除,它们不会像数据构造函数那样对运行时性能造成影响。但是,它们仍然提供了完全独立类型的所有好处以及您想关联的任何抽象。例如,列表数据类型可以形成单子的两种不同方式。其中一种内置于语言中,但如果您想使用另一种方式,则应该使用newtype。 - mightybyte
1
很好的解释!我不明白的是,如果newtype在编译后被擦除,并且运行时使用相同的表示来表示旧类型和新类型,我们如何仍然能够为旧类型和新类型定义实例?运行时如何理解要使用哪个实例? - Konstantin Milyutin
5
在运行时,所有类型都被擦除了,它们在编译时完全解析,在编译期间newtype显然��没有被擦除。 - semicolon
7
我之前也和你一样有同样的疑问。当人们说类型被抹除时,他们忽略了一件事情没有被抹除,那就是一个内存单元,用于字典查找以决定为给定数据使用哪个实例方法。人们争论这个单元不是一个“类型”,我认为这取决于你的观点,但是就是这样。 - Gabriel L.
显示剩余4条评论

0

它们在语义上有所不同。

  • data 定义了一个 GADT(积类型、和类型等)
  • newtype 定义了一个同构。

当你不关心它是否同构时,应该使用 data,即使它只有一个字段。

例如,

data Student = Student {
    age :: Int
}

如果在这个问题域中,你只需要处理关于学生的年龄信息,那么你应该使用 data 而不是 newtype,因为你从来没有意思是一个学生应该同构于一个年龄。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接