在Haskell中,你有产品类型,你有元组。
如果您不想将专用类型与值相关联,则使用元组,如果您愿意,可以使用产品类型。
但我觉得产品类型的符号存在冗余
data Foo = Foo (String, Int, Char)
data Bar = Bar String Int Char
为什么有这两种符号?有没有你更喜欢另一个的情况?
我猜你在使用元组时不能使用记录符号,但这只是一个方便的问题。另一件事可能是元组中的顺序概念,而不是产品类型,但我认为这仅仅归功于函数fst
和snd
的命名。
答案 0 :(得分:7)
@chi's answer是关于Haskell评估模型的技术差异。我希望能让你深入了解这种类型编程的哲学。
在类别理论中,我们通常使用对象"直到同构"。您的Bar
当然与(String, Int, Char)
同构,因此从分类的角度来看,它们是相同的。
bar_tuple :: Iso' Bar (String, Int, Char)
bar_tuple = iso to from
where to (Bar s i c) = (s, i, c)
from (s, i, c) = Bar s i c
在某种意义上,元组是柏拉图式的产品类型,因为除了作为不同价值的集合之外,它们没有任何意义。所有其他产品类型都可以映射到普通的旧元组。
那么为什么不在任何地方使用元组,当所有Haskell类型最终归结为一定数量的产品?它是关于沟通的。正如Martin Fowler所说,
任何傻瓜都可以编写计算机可以理解的代码。优秀的程序员编写人类可以理解的代码。
名字很重要!写下自定义产品类型,如
data Customer = Customer { name :: String, address :: String }
将含义的类型Customer
包含在阅读代码的人身上,而不像(String, String)
这意味着"两个字符串"。
当您想通过隐藏数据表示并使用智能构造函数来强制执行不变量时,自定义类型特别有用:
newtype NonEmpty a = NonEmpty [a]
nonEmpty :: [a] -> Maybe (NonEmpty a)
nonEmpty [] = Nothing
nonEmpty xs = Just (NonEmpty xs)
现在,如果您不导出NonEmpty
构造函数,则可以强制用户通过nonEmpty
智能构造函数。如果某人给你一个NonEmpty
值,你可以放心地假设它至少有一个元素。
当然,您可以将Customer
表示为引擎盖下的元组,并展示具有唤起意义的字段访问器,
newtype Customer = Bar (String, String)
name, address :: Customer -> String
name (Customer (n, a)) = n
address (Customer (n, a)) = a
但是这并没有给你带来太多的好处,除了将Customer
转换为元组现在更便宜(例如,如果您正在编写对性能敏感的代码,那么使用面向元组的API)。
如果您的代码旨在解决某个特定问题 - 这当然是编写代码的重点 - 那么不仅要解决问题,还要让它看起来像你已经解决了它。有人 - 也许你在几年内 - 将不得不阅读这些代码并理解它,而不是先验知道它是如何工作的。在这方面,自定义类型是非常重要的通信工具。
答案 1 :(得分:4)
类型
data Foo = Foo (String, Int, Char)
表示双重提升的元组。它的值包括
undefined
Foo undefined
Foo (undefined, undefined, undefined)
etc.
这通常很麻烦。因此,在实际代码中看到这样的定义很少见。我们要么有简单的数据类型
data Foo = Foo String Int Char
或newtype
s
newtype Foo = Foo (String, Int, Char)
使用newtype
可能同样不方便,但至少可以
不会双重提升元组:undefined
和Foo undefined
现在是相等的值。
newtype
还可以在两个方向上提供普通元组和Foo
之间的零成本转换。
您可以看到正在使用的此类newtype
例如当程序员需要一个类型类的不同实例时,而不是已经与元组关联的实例。或者,它可能用于智能构造函数"成语。
答案 2 :(得分:0)
我不希望Foo
中使用的模式频繁出现。构造函数的作用略有不同:Foo :: (String, Int, Char) -> Foo
而不是Bar :: String -> Int -> Char -> Bar
。然后,Foo undefined
和Foo (undefined, ..., ...)
严格来说是不同的事情,而你却错过Bar
中的一个未定义级别。