"data" 和 "type" 关键字有什么区别?

What's the difference between the "data" and "type" keywords?

datatype 关键字总是让我感到困惑。

我想知道datatype有什么区别以及如何使用它们。

使用 data 创建 new 数据类型并为其声明一个构造函数:

data NewData = NewDataConstructor

使用 type 您只定义一个别名:

type MyChar = Char

type 的情况下,您可以将 MyChar 类型的值传递给需要 Char 的函数,反之亦然,但您不能为 data MyChar = MyChar Char 执行此操作.

type 声明了一个 类型的同义词 。类型同义词是现有类型的新名称。例如,String 是这样定义的 in the standard library:

type String = [Char]

StringChar 列表的另一个名称。 GHC 将在编译时用 [Char] 替换程序中所有 String 的用法。

明确地说,String 字面意思是 Char 的列表。这只是一个别名。您可以对 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 值可以是 TrueFalse。数据类型支持模式匹配,允许您对数据类型的值执行运行时案例分析。

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 类型 包含 Char 列表(而不是类型同义词),你不会能够在其上使用任何列表功能。

data String = MkString [Char]
myString = MkString ['h', 'e', 'l', 'l', 'o']
myReversedString = reverse myString  -- type error

还有一种类型声明:newtype。这很像 data 声明 - 它引入了一种与任何其他类型分开的新数据类型,并且可以进行模式匹配 - 除了你被限制为具有单个字段的单个构造函数。换句话说,newtype 是一个 data 类型,它包含一个现有类型。

重要的区别是 newtypecost:编译器承诺 newtype 的表示方式与其包装的类型相同.打包或解包 newtype 没有运行时成本。这使得 newtypes 可用于在值之间进行 管理(而不是 结构)区分。

newtypes 与类型 classes 交互良好。例如,考虑 Monoid,具有组合元素 (mappend) 和特殊 'empty' 元素 (mempty) 的类型的 class。 Int 可以通过多种方式变成 Monoid,包括与 0 的加法和与 1 的乘法。我们如何选择将哪个用于 [=51= 的可能 Monoid 实例]?最好不要表达偏好,并使用 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

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,datanewtype 都创建了一个不透明的 抽象 。它们更像是 OO 中的 class 构造函数:即使一些值只是根据单个数字 实现 ,它不一定 表现 喜欢这样的数字。例如,

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