我正在尝试在 Haskell 中创建一个函数,该函数以Resp
BNF 和 Haskell 类型之间的奇怪组合返回下面所示的类型。
elem ::= String | (String, String, Resp)
Resp ::= [elem]
我的问题是(a)如何在 Haskell 中定义这种类型,以及(b)是否有一种方法可以这样做而不必被迫使用自定义构造函数,例如,Node
而不是仅使用元组和数组。
你说“各种各样的关键字(数据、类型、新类型)让我感到困惑”。这是 Haskell 中数据构造关键字的快速入门。
创建新类型的规范方法是使用data
关键字。Haskell 中的通用类型是产品类型的联合,每个产品类型都标有构造函数。例如,anEmployee
可能是一线工人(有姓名和薪水)或经理(有姓名、薪水和报告列表)。
我们使用String
类型来表示员工的姓名,并使用Int
类型来表示薪水。报告列表只是Employee
s 的列表。
data Employee = Worker String Int
| Manager String Int [Employee]
type
关键字用于创建类型同义词,即相同类型的替代名称。这通常用于使源更容易理解。例如,我们可以Name
为员工姓名(实际上只是 a String
)和Salary
薪水(只是Int
s)和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
字段中使用任何旧的(例如,地址)。编译器不区分Name
s和plain old String
s,这引入了一类潜在的错误。
使用newtype
关键字,我们可以使编译器强制String
在字段中使用的唯一 s 是Name
明确标记为Name
s的那些
newtype Name = Name String
newtype Salary = Salary Int
newtype Reports = Reports [Employee]
data Employee = Worker Name Salary
| Manager Name Salary Reports
现在,如果我们尝试String
在Name
字段中输入 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
不是数字类型,因此您不能将它们加在一起(或者至少,您还没有告诉编译器如何将它们加在一起)。一种选择是定义一个函数,Int
从Salary
getSalary :: Salary -> Int
getSalary (Salary sal) = sal
但事实上,如果您在声明newtype
s时使用记录语法,Haskell 会为您编写这些
data Salary = Salary { getSalary :: Int }
现在你可以写
>>> getSalary kate'sSalary + getSalary fred'sSalary
90000
第1部分:
data Elem = El String | Node String String Resp
type Resp = [Elem]
第 2 部分:嗯……有点。不满意的答案是:您不应该这样做,因为这样做的类型安全性较低。更直接的答案是Elem
需要它自己的构造函数,但Resp
很容易定义为上面的类型同义词。不过,我会推荐
newtype Resp = Resp { getElems :: [Elem] }
这样您就不能将Elem
s 的一些随机列表与Resp
. 这也为您提供了该功能getElems
,因此您不必在单个构造函数上进行尽可能多的模式匹配。newtype
基本上让 Haskell 知道它应该在运行时摆脱构造函数的开销,因此没有额外的间接,这很好。