4

我正在尝试在 Haskell 中创建一个函数,该函数以RespBNF 和 Haskell 类型之间的奇怪组合返回下面所示的类型。

elem ::= String | (String, String, Resp)
Resp ::= [elem]

我的问题是(a)如何在 Haskell 中定义这种类型,以及(b)是否有一种方法可以这样做而不必被迫使用自定义构造函数,例如,Node而不是仅使用元组和数组。

4

2 回答 2

12

你说“各种各样的关键字(数据、类型、新类型)让我感到困惑”。这是 Haskell 中数据构造关键字的快速入门。

数据

创建新类型的规范方法是使用data关键字。Haskell 中的通用类型是产品类型的联合,每个产品类型都标有构造函数。例如,anEmployee可能是一线工人(有姓名和薪水)或经理(有姓名、薪水和报告列表)。

我们使用String类型来表示员工的姓名,并使用Int类型来表示薪水。报告列表只是Employees 的列表。

data Employee = Worker  String Int
              | Manager String Int [Employee]

类型

type关键字用于创建类型同义词,即相同类型的替代名称。这通常用于使源更容易理解。例如,我们可以Name为员工姓名(实际上只是 a String)和Salary薪水(只是Ints)和Reports报告列表声明一个类型。

type Name    = String
type Salary  = Int
type Reports = [Employee]

data Employee = Worker  Name Salary
              | Manager Name Salary Reports

新型

newtype关键字类似于type关键字,但它增加了额外的类型安全性。上一段代码的一个问题是,尽管 worker 是 aName和 a的组合Salary,但没有什么可以阻止您String在该Name字段中使用任何旧的(例如,地址)。编译器不区分Names和plain old Strings,这引入了一类潜在的错误。

使用newtype关键字,我们可以使编译器强制String在字段中使用的唯一 s 是Name明确标记为Names的那些

newtype Name    = Name String
newtype Salary  = Salary Int
newtype Reports = Reports [Employee]

data Employee = Worker  Name Salary
              | Manager Name Salary Reports

现在,如果我们尝试StringName字段中输入 a 而不显式标记它,我们会收到类型错误

>>> let kate = Worker (Name "Kate") (Salary 50000)     -- this is ok
>>> let fred = Worker "18 Tennyson Av." (Salary 40000) -- this will fail

<interactive>:10:19:
    Couldn't match expected type `Name' with actual type `[Char]'
    In the first argument of `Worker', namely `"18 Tennyson Av."'
    In the expression: Worker "18 Tennyson Av." (Salary 40000)
    In an equation for `fred':
        fred = Worker "18 Tennyson Av." (Salary 40000)

这样做的好处是因为编译器知道 aName实际上只是 a String,所以它优化掉了额外的构造函数,所以这和使用声明一样有效type——额外的类型安全是“免费的”。这需要一个重要的限制—— anewtype只有一个构造函数和一个值。否则编译器将不知道哪个构造函数或值是正确的同义词!

使用newtype声明的一个缺点是现在 aSalary不再只是 a Int,您不能直接将它们加在一起。例如

>>> let kate'sSalary = Salary 50000
>>> let fred'sSalary = Salary 40000
>>> kate'sSalary + fred'sSalary

<interactive>:14:14:
    No instance for (Num Salary)
      arising from a use of `+'
    Possible fix: add an instance declaration for (Num Salary)
    In the expression: kate'sSalary + fred'sSalary
    In an equation for `it': it = kate'sSalary + fred'sSalary

有点复杂的错误消息告诉您 aSalary不是数字类型,因此您不能将它们加在一起(或者至少,您还没有告诉编译器如何将它们加在一起)。一种选择是定义一个函数,IntSalary

getSalary :: Salary -> Int
getSalary (Salary sal) = sal

但事实上,如果您在声明newtypes时使用记录语法,Haskell 会为您编写这些

data Salary = Salary { getSalary :: Int }

现在你可以写

>>> getSalary kate'sSalary + getSalary fred'sSalary
90000
于 2013-07-15T07:59:59.143 回答
5

第1部分:

data Elem = El String | Node String String Resp
type Resp = [Elem]

第 2 部分:嗯……有点。不满意的答案是:您不应该这样做,因为这样做的类型安全性较低。更直接的答案是Elem需要它自己的构造函数,但Resp很容易定义为上面的类型同义词。不过,我会推荐

newtype Resp = Resp { getElems :: [Elem] }

这样您就不能将Elems 的一些随机列表与Resp. 这也为您提供了该功能getElems,因此您不必在单个构造函数上进行尽可能多的模式匹配。newtype基本上让 Haskell 知道它应该在运行时摆脱构造函数的开销,因此没有额外的间接,这很好。

于 2013-07-15T05:51:27.463 回答