关于Haskell中产品类型和元组的冗余

时间:2017-03-27 12:36:39

标签: haskell tuples

在Haskell中,你有产品类型,你有元组。

如果您不想将专用类型与值相关联,则使用元组,如果您愿意,可以使用产品类型。

但我觉得产品类型的符号存在冗余

data Foo = Foo (String, Int, Char)
data Bar = Bar String Int Char

为什么有这两种符号?有没有你更喜欢另一个的情况?

我猜你在使用元组时不能使用记录符号,但这只是一个方便的问题。另一件事可能是元组中的顺序概念,而不是产品类型,但我认为这仅仅归功于函数fstsnd的命名。

3 个答案:

答案 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可能同样不方便,但至少可以 不会双重提升元组:undefinedFoo undefined现在是相等的值。

newtype还可以在两个方向上提供普通元组和Foo之间的零成本转换。

您可以看到正在使用的此类newtype例如当程序员需要一个类型类的不同实例时,而不是已经与元组关联的实例。或者,它可能用于智能构造函数"成语。

答案 2 :(得分:0)

我不希望Foo中使用的模式频繁出现。构造函数的作用略有不同:Foo :: (String, Int, Char) -> Foo而不是Bar :: String -> Int -> Char -> Bar。然后,Foo undefinedFoo (undefined, ..., ...)严格来说是不同的事情,而你却错过Bar中的一个未定义级别。