Haskell术语:类型与数据类型的含义,它们是同义词吗?

8
我正在阅读《Haskell表达之学校》这本书,第5章开头的第56页,我看到了“多态数据类型”和“多态类型”这两个术语。
这两个术语是指同一个概念吗?
它们是同义词吗?
还是两者之间有区别?如果有,是什么?

与 C 语言进行粗略比较,如果您熟悉该语言,则可以将 data 视为 structunion 的组合,而 type 则类似于 typedef。然而,Haskell 数据类型比其 C 语言对应物更加强大(data 允许使用更多构造,因为有所有可用的扩展)。 - didierc
1
@didierc,粗略地说,它们就像是一个带标签的联合体,但正如你所说,它们比这更复杂一些。 - Wes
@Wes,是的,我正在考虑那个问题,谢谢你提醒! - didierc
3个回答

22
在 Haskell 中,类型(type)是一种语法,它可以紧跟在 :: 的右侧,将左侧的表达式分类。每个类型语法组件本身都有一个种类,其中用于分类表达式的类型种类为 *。然而,一些人乐意使用“类型”这个词来指代类型语法中的任何组件,无论它们的种类是否允许对表达式进行分类。
类型的语法可以通过各种声明形式进行扩展:
  1. 类型同义词(type synonym),例如:type Foo x y z = [x] -> IO (y, z),添加了完全应用形式 Foo x y z 的类型组件,并根据其定义方程式进行宏扩展。
  2. 数据声明(data declaration),例如:data Goo x y z = ThisGoo x | ThatGoo (Goo y z x),引入了新的类型构造器符号 Goo 到类型的语法中,该符号用于构建由数据构造器(data constructors)生成的值所分类的类型,这里的数据构造器是 ThisGooThatGoo
  3. 新类型声明(newtype declaration),例如:newtype Noo x y z = MkNoo (x, [y], z),创建了一个现有类型的副本,并在类型的语法中与原始类型区分开来。
如果一个类型包含可以替换为其他类型成分的类型变量,则它是多态的。使用多态类型分类的值可以被专门化为类型变量的任意替代实例。例如,append(++) :: [a] -> [a] -> [a]适用于元素类型相同的列表,但任何类型都可以。具有多态类型的值通常称为"多态值"。
有时,“数据类型”仅仅是指由"data"声明引入的类型。在这个意义上,所有数据类型都是类型,但并不是所有类型都是数据类型。不属于数据类型的类型的例子包括IO()和Int -> Int。而在这种意义下,Int不是数据类型,它是一个硬编码的原始类型。为了避免混淆,一些人将这些类型称为“代数数据类型”,因为构造函数提供了一个代数,即“通过组合其他值来构建值的一堆操作”。"多态数据类型"是一个带有类型变量的数据类型,例如[(a, Bool)],与[Int]相反。有时人们谈论"声明一个多态数据类型"或者说类似"Maybe是一个多态数据类型",当他们真正意思是类型构造器有参数(因此可以用来形成多态类型),严格地说,你确实声明了一个多态数据类型,但并不是任何旧的多态数据类型,而是一个类型构造器应用于形式参数)。
当然,按照某种意义上来说,由类型分类的所有一等值在某种程度上都是“数据”,而在Haskell中,类型不用于分类任何不是一等值的东西,因此从这个宽松的角度来看,每个“类型”都是一个“数据类型”。这个区别在存在不同于数据具有类型的语言(例如Java中的方法)中变得更有意义。

非正式用法通常介于两者之间,定义并不十分清晰。人们经常在某种功能或过程与它们操作的“数据”之间进行某种区分。或者他们可能认为数据是“基于它们的制作方式理解的”(通过模式匹配暴露它们的表示),而不是“基于它们的使用方式理解的”。这种“数据”的最后一种用法与抽象数据类型的概念有些不协调,抽象数据类型是一种隐藏底层内容表示的类型。因此,隐藏表示的抽象数据类型与暴露表示的代数数据类型形成了相当强烈的对比,这就是为什么“ADT”被随意用作两者的缩写的原因,这非常不幸。

总之,我很遗憾地说,这种用法存在一定的含糊性。


你能否在Haskell中添加一个抽象数据类型的例子?根据这个定义,List是一个抽象数据类型吗? - Sibi
1
在Haskell中,列表不是抽象数据类型,因为它们的表示是公开的:您可以通过模式匹配访问它们。库类型Set a是抽象数据类型的一个示例:您可以获得接口(带有空集、联合、交集、各种测试等),但无法访问实际表示(实际上可能只使用列表,但据我所知,它实际上是一些巧妙的二叉树结构)。 - pigworker
所以答案是,如果我理解正确的话,没有人真正知道区别。不幸的是,Hudak在他的书中没有定义“数据类型”这个术语,但仍然使用它,我本来期望这位备受尊敬的作者更加精确。 - jhegedus
2
我认为答案并不是没有人知道区别,而是“数据类型”并不总是一个技术术语。或者说它有很多种含义。而且通常它的确切含义并不值得捕捉。尽管如此,这里的答案确实全面地涵盖了我所能想到的所有精确含义。 - J. Abrahamson

3
在这种情况下,“数据类型”和“类型”是同义词。然而,我承认,可能会出现混淆,因为Haskell有两个关键字“data”和“type”,它们执行两个非常不同的功能。为了保持区分清晰,重要的是要注意上下文。每当您谈论“签名中的类型”或“一般类型”的时候,“数据类型”和“类型”几乎总是指同一件事情。每当您谈论“在代码中声明类型”的时候,就可能存在差异。
用“data”声明的类型是新的、用户定义的类型,所以您可以做一些像...这样的事情。
data Status = Ready | NotReady | Exploded

在Haskell中,ReadyNotReadyExploded是新的构造函数。

另一方面,有一个type关键字,它仅仅创建了一个对现有类型的别名

type Status = String

ready, notReady, exploded :: Status
ready = "Ready"
notReady = "NotReady"
exploded = "Exploded"

在这里,“Status”只是“String”的别名,您可以在任何使用“String”的地方使用“Status”,反之亦然。没有构造函数,只有预先构建的值可供使用。这种方法远不如安全,如果您使用类似于此的东西,则最终会遇到错误。 “Type”声明通常用于使某些参数更清楚其目的,例如:
type FilePath = String

这是GHC内置的别名,如果你看到一个函数
doSomething :: FilePath -> IO ()

那么你立即知道要传递一个文件名,与之相比

doSomething :: String -> IO ()

你对这个函数的作用一无所知,除了“做某些事情”之外。它们通常也用于减少输入,例如:

type Point = (Double, Double)

现在你可以在类型签名中使用Point而不是(Double, Double),这样更短更易读。
总结一下,data声明了一个全新的类型,完全根据您的需求定制,而type应该被重命名为alias,以便于初学Haskell的人们避免混淆。

在我看来,typedata/type/newtype 中唯一有意义的。其他两个由于使用 = 的荒谬用法和类型级别和项级别标识符的混合而语法上令人困惑。type 是一个直接的类型级自然函数(自然变换)。 - jberryman

0

typedata type指的是同一个概念,polymorphic typepolymorphic data type也是如此。

为了说明两个短语可以一起使用(没有任何意义上的区别),考虑以下表达式。

data Maybe a = Just a | Nothing

我可以说,我刚刚定义了一个多态数据类型Maybe,它具有多态类型参数a


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