Haskell中类型与记录字段的案例

时间:2016-08-21 20:30:17

标签: haskell

以下两段代码看起来非常相似。但是必须存在一些差异,我希望有人可以指出它们。

data Animal = Cat | Dog
speak :: Animal -> String
speak Cat = "meowh"
speak Dog = "wouf"

data Animal = Animal { speak :: String }
cat = Animal { speak = "meowh"}
dog = Animal { speak = "wouf" }

3 个答案:

答案 0 :(得分:25)

好问题!您已经触及了软件工程的一个基本问题的核心。 Per Wadler,它被称为the Expression Problem,并且粗略地总结了这个问题:

  

是否可以轻松添加新操作或新类型的数据?

您的第一个示例使轻松地向现有动物添加新操作。我们可以通过各种方式处理Animal值,而无需更改Animal的定义:

numberOfTeeth :: Animal -> Int
numberOfTeeth Cat = 30
numberOfTeeth Dog = 42

food :: Animal -> String
food Cat = "Fish"
food Dog = "Sausages"  -- probably stolen from a cartoon butcher

缺点是它很难添加动物的新类型。您必须向Animal添加新构造函数并更改所有现有操作:

data Animal = Cat | Dog | Crocodile

speak :: Animal -> String
speak Cat = "miaow"
speak Dog = "woof"
speak Crocodile = "RAWR"

numberOfTeeth :: Animal -> Int
numberOfTeeth Cat = 30
numberOfTeeth Dog = 42
numberOfTeeth Crocodile = 100000  -- I'm not a vet

food :: Animal -> String
food Cat = "Fish"
food Dog = "Sausages"
food Crocodile = "Human flesh"

您的第二个示例翻转矩阵,使轻松添加新类型

crocodile = Animal { speak = "RAWR" }

难以添加新功能 - 这意味着向Animal添加新字段并更新所有现有动物。

data Animal = Animal {
    speak :: String,
    numberOfTeeth :: Int,
    food :: String
}
cat = Animal {
    speak = "miaow",
    numberOfTeeth = 30,
    food = "Fish"
}
dog = Animal {
    speak = "woof",
    numberOfTeeth = 42,
    food = "Sausages"
}
crocodile = Animal {
    speak = "RAWR",
    numberOfTeeth = 100000,
    food = "Human flesh"
}

不要低估表达问题有多大的交易!如果您正在处理已发布的库,那么您可能会发现自己正在与您在代码库中从未遇到的人定义的操作或类型进行竞争,而这些操作或类型无法更改。您必须仔细考虑您希望人们使用您的系统的方式,并决定如何确定图书馆的设计方向。

多年来,聪明人发明了许多解决表达式问题的聪明方法,以支持新操作新类型。使用最现代编程语言的最先进功能,这些解决方案往往很复杂。在现实世界中,这只是工程师必须考虑的另一个权衡 - 解决表达问题值得代码复杂性导致它?

答案 1 :(得分:1)

首先,让我们为这些类型指定名称,这些名称更接近其本质:

data AnimalType =
  Cat | Dog

newtype Phrase =
  Phrase { phrase :: String }

已经很明显它们非常不同并且明显可以隔离。

这里的一个短语是一种更普遍的方式。它不仅可以说是动物,也可以说是机器人。但更多的是它不仅可以说出来,而且还可以通过诸如上限等操作进行转换。这样的操作对于AnimalType类型没有任何意义。

AnimalType OTOH有其自身的好处。通过匹配类型,您可以选择动物需要的食物类型或其大小等。您无法在Phrase上执行此操作。

您也可以在应用程序中单独共存这两种类型,并且从更具体到更具体的转换(并因此具有依赖性):

module Animal where

import qualified Phrase

speakPhrase :: Animal -> Phrase.Phrase
speakPhrase =
  Phrase.Phrase . speak

导致您混淆的是您的问题缺乏应用程序的上下文。一旦您将自己提供给自己,您就会获得有关您实际需要做什么以及它将对哪些数据进行操作的信息。

答案 2 :(得分:0)

我们称之为已关闭的第一个代码段。这意味着它不可扩展,而第二个片段是:我们可以创建尽可能多的Animal个对象,但不限于CatDog

但你是对的,除了模式匹配之外,它们在我们使用它的方式上非常相似。您可以与第一个代码段进行模式匹配,但不能与第二个代码段匹配。但是,如果从Animal类型类派生Eq的第二个定义以允许与catdog进行比较,则不会出现问题。