"data" 和 "type" 关键字有什么区别?
What's the difference between the "data" and "type" keywords?
data
和 type
关键字总是让我感到困惑。
我想知道data
和type
有什么区别以及如何使用它们。
使用 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]
String
是 Char
列表的另一个名称。 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
值可以是 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
类型 包含 的 Char
列表(而不是类型同义词),你不会能够在其上使用任何列表功能。
data String = MkString [Char]
myString = MkString ['h', 'e', 'l', 'l', 'o']
myReversedString = reverse myString -- type error
还有一种类型声明:newtype
。这很像 data
声明 - 它引入了一种与任何其他类型分开的新数据类型,并且可以进行模式匹配 - 除了你被限制为具有单个字段的单个构造函数。换句话说,newtype
是一个 data
类型,它包含一个现有类型。
重要的区别是 newtype
的 cost:编译器承诺 newtype
的表示方式与其包装的类型相同.打包或解包 newtype
没有运行时成本。这使得 newtype
s 可用于在值之间进行 管理(而不是 结构)区分。
newtype
s 与类型 classes 交互良好。例如,考虑 Monoid
,具有组合元素 (mappend
) 和特殊 'empty' 元素 (mempty
) 的类型的 class。 Int
可以通过多种方式变成 Monoid
,包括与 0 的加法和与 1 的乘法。我们如何选择将哪个用于 [=51= 的可能 Monoid
实例]?最好不要表达偏好,并使用 newtype
s 来启用任何一种用法而无需运行时成本。释义 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,data
和 newtype
都创建了一个不透明的 抽象 。它们更像是 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
。
data
和 type
关键字总是让我感到困惑。
我想知道data
和type
有什么区别以及如何使用它们。
使用 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]
String
是 Char
列表的另一个名称。 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
值可以是 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
类型 包含 的 Char
列表(而不是类型同义词),你不会能够在其上使用任何列表功能。
data String = MkString [Char]
myString = MkString ['h', 'e', 'l', 'l', 'o']
myReversedString = reverse myString -- type error
还有一种类型声明:newtype
。这很像 data
声明 - 它引入了一种与任何其他类型分开的新数据类型,并且可以进行模式匹配 - 除了你被限制为具有单个字段的单个构造函数。换句话说,newtype
是一个 data
类型,它包含一个现有类型。
重要的区别是 newtype
的 cost:编译器承诺 newtype
的表示方式与其包装的类型相同.打包或解包 newtype
没有运行时成本。这使得 newtype
s 可用于在值之间进行 管理(而不是 结构)区分。
newtype
s 与类型 classes 交互良好。例如,考虑 Monoid
,具有组合元素 (mappend
) 和特殊 'empty' 元素 (mempty
) 的类型的 class。 Int
可以通过多种方式变成 Monoid
,包括与 0 的加法和与 1 的乘法。我们如何选择将哪个用于 [=51= 的可能 Monoid
实例]?最好不要表达偏好,并使用 newtype
s 来启用任何一种用法而无需运行时成本。释义 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,data
和 newtype
都创建了一个不透明的 抽象 。它们更像是 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
。