data
和type
关键字总是让我感到困惑。
我想知道data
和type
之间的区别以及如何使用它们。
答案 0 :(得分:18)
type
声明类型同义词。类型同义词是现有类型的新名称。例如,这就是String
的定义in the standard library:
type String = [Char]
String
是Char
列表的另一个名称。 GHC将在编译时用String
替换程序中[Char]
的所有用法。
要明确的是,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
s列表(而非类型同义词),则您将无法使用任何列表都在其上运行。
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
对于在值之间进行管理(而非结构)区分非常有用。
newtype
与类型类很好地互动。例如,考虑Monoid
,类型类,其中包含组合元素(mappend
)和特殊“空”元素(mempty
)的方法。 Int
可以通过多种方式设置为Monoid
,包括添加0和乘以1.我们如何选择使用哪一个Monoid
Int
实例}?最好不要表达首选项,并使用newtype
来启用任何使用而不需要运行时成本。释义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
答案 1 :(得分:2)
readDouble()
的工作原理与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
,反之亦然;什么都不会改变。
OTOH,f'
和data
都会创建一个不透明的抽象。它们更像是OO中的类构造函数:即使某个值实现只是单个数字,它也不一定表现就像这样的数字。例如,
newtype
虽然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
不同。
答案 2 :(得分:1)
使用data
创建 new 数据类型并为其声明构造函数:
data NewData = NewDataConstructor
使用type
,您只需定义一个别名:
type MyChar = Char
在type
案例中,您可以将MyChar
类型的值传递给期望Char
的函数,反之亦然,但不能为data MyChar = MyChar Char
执行此操作。< / p>