【发布时间】:2026-01-30 22:25:01
【问题描述】:
data 和 type 关键字总是让我感到困惑。
我想知道data和type有什么区别以及如何使用它们。
【问题讨论】:
data 和 type 关键字总是让我感到困惑。
我想知道data和type有什么区别以及如何使用它们。
【问题讨论】:
type 声明了一个类型同义词。类型同义词是现有类型的新名称。例如,String 是这样定义的 in the standard library:
type String = [Char]
String 是Chars 列表的另一个名称。 GHC 将在编译时用[Char] 替换程序中所有String 的用法。
明确地说,String 字面意思是Chars 的列表。这只是一个别名。您可以在String 值上使用所有标准列表函数:
-- length :: [a] -> Int
ghci> length "haskell"
7
-- reverse :: [a] -> [a]
ghci> reverse "functional"
"lanoitcnuf"
data 声明了一个新数据类型,与类型同义词不同,它不同于任何其他类型。数据类型有许多构造函数来定义你的类型的可能情况。例如,Bool 是这样定义的 in the standard library:
data Bool = False | True
Bool 值可以是 True 或 False。数据类型支持模式匹配,允许您对数据类型的值执行运行时案例分析。
yesno :: Bool -> String
yesno True = "yes"
yesno False = "no"
data 类型可以有多个构造函数(与Bool 一样),可以由其他类型参数化,可以在其中包含其他类型,并且可以递归引用自己。这是一个证明这一点的异常模型; Error a 包含 a 类型的错误消息,并且可能是导致它的错误。
data Error a = Error { value :: a, cause :: Maybe (Error a) }
type ErrorWithMessage = Error String
myError1, myError2 :: ErrorWithMessage
myError1 = Error "woops" Nothing
myError2 = Error "myError1 was thrown" (Just myError1)
重要的是要意识到data 声明了一个不同于系统中任何其他类型的新类型。如果String 被声明为data 类型包含Chars 列表(而不是类型同义词),您将无法在其上使用任何列表函数。
data String = MkString [Char]
myString = MkString ['h', 'e', 'l', 'l', 'o']
myReversedString = reverse myString -- type error
还有另外一种类型声明:newtype。这很像data 声明——它引入了一种与任何其他类型分开的新数据类型,并且可以进行模式匹配——除非你被限制为具有单个字段的单个构造函数。换句话说,newtype 是封装了现有类型的 data 类型。
重要的区别是newtype 的成本:编译器承诺newtype 的表示方式与其包装的类型相同。打包或解包newtype 没有运行时成本。这使得newtypes 有助于在值之间进行管理(而不是结构)区分。
newtypes 可以很好地与类型类交互。例如,考虑Monoid,该类型具有组合元素(mappend)和特殊“空”元素(mempty)的方式。 Int 可以通过多种方式转换为Monoid,包括与 0 的加法和与 1 的乘法。我们如何选择将哪一种用于Monoid 的可能Int 实例?最好不要表达偏好,并使用newtypes 来启用任一使用,而无需运行时成本。转述the standard library:
-- introduce a type Sum with a constructor Sum which wraps an Int, and an extractor getSum which gives you back the Int
newtype Sum = Sum { getSum :: Int }
instance Monoid Sum where
(Sum x) `mappend` (Sum y) = Sum (x + y)
mempty = Sum 0
newtype Product = Product { getProduct :: Int }
instance Monoid Product where
(Product x) `mappend` (Product y) = Product (x * y)
mempty = Product 1
【讨论】:
newtypes 表达每个可能的选择明确,没有运行时成本”。
使用data,您可以创建 new 数据类型并为其声明构造函数:
data NewData = NewDataConstructor
使用type,您只需定义一个别名:
type MyChar = Char
在type 的情况下,您可以将MyChar 类型的值传递给期望Char 的函数,反之亦然,但对于data MyChar = MyChar Char,您不能这样做。
【讨论】:
type 的工作方式与let 类似:它允许您为某事物指定一个可重用的名称,但该名称将始终有效,就像您已内联定义一样。所以
type ℝ = Double
f :: ℝ -> ℝ -> ℝ
f x y = let x2 = x^2
in x2 + y
行为方式与
完全相同f' :: Double -> Double -> Double
f' x y = x^2 + y
如:您可以在代码中的任何位置将f 替换为f',反之亦然;什么都不会改变。
OTOH,data 和 newtype 创建了一个不透明的抽象。它们更像是 OO 中的类构造函数:即使某些值实现 只是根据单个数字,它不一定表现 像这样的数字。例如,
newtype Logscaledℝ = LogScaledℝ { getLogscaled :: Double }
instance Num LogScaledℝ where
LogScaledℝ a + LogScaledℝ b = LogScaledℝ $ a*b
LogScaledℝ a - LogScaledℝ b = LogScaledℝ $ a/b
LogScaledℝ a * LogScaledℝ b = LogScaledℝ $ a**b
在这里,虽然Logscaledℝ 在数据方面仍然只是一个Double 数字,但它的行为显然与Double 不同。
【讨论】: