我试图在Haskell中创建一个函数,在BNF和Haskell类型之间的奇怪混合中返回下面说明的Resp
类型。
elem ::= String | (String, String, Resp)
Resp ::= [elem]
我的问题是(a)如何在Haskell中定义这种类型,以及(b)如果有一种方法这样做而不必强制使用自定义构造函数,例如Node
,而只使用元组和阵列。
答案 0 :(得分:9)
你说“各种关键词(数据,类型,新类型)一直让我感到困惑”。这是Haskell中数据构造关键字的快速入门。
创建新类型的规范方法是使用data
关键字。 Haskell中的一般类型是产品类型的联合,每个产品类型都用构造函数标记。例如,Employee
可能是一个行工作者(具有名称和工资)或经理(具有名称,工资和报告列表)。
我们使用String
类型来表示员工的姓名,使用Int
类型来表示salaray。报告列表只是Employee
的列表。
data Employee = Worker String Int
| Manager String Int [Employee]
type
关键字用于创建类型同义词,即同一类型的备用名称。这通常用于使源更容易理解。例如,我们可以为员工姓名(实际上只是Name
)和工资String
声明类型Salary
,并且{Int
s) {1}}获取报告列表。
Reports
type Name = String
type Salary = Int
type Reports = [Employee]
data Employee = Worker Name Salary
| Manager Name Salary Reports
关键字类似于newtype
关键字,但它增加了额外的类型安全性。上一段代码的一个问题是,尽管工作者是type
和Name
的组合,但没有什么可以阻止您使用{Salary
中的任何旧String
1}}字段(例如,地址)。编译器不区分Name
和普通旧Name
,这引入了一类潜在的错误。
使用String
关键字,我们可以让编译器强制执行newtype
字段中唯一可以String
明确标记为Name
的字段Name
newtype Name = Name String
newtype Salary = Salary Int
newtype Reports = Reports [Employee]
data Employee = Worker Name Salary
| Manager Name Salary Reports
现在,如果我们尝试在String
字段中输入Name
而未明确标记,则会出现类型错误
>>> 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)
这有什么好处,因为编译器知道Name
实际上只是String
,它会优化掉额外的构造函数,所以这和使用{{1}一样有效声明 - 额外的类型安全“免费”。这需要一个重要的限制 - a type
只有一个构造函数只有一个值。否则编译器将不知道哪个构造函数或值是正确的同义词!
使用newtype
声明的一个缺点是,现在newtype
不再仅仅是Salary
,您无法直接将它们添加到一起。例如
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
不是数字类型,因此您无法将它们添加到一起(或者至少,您没有告诉编译器如何将它们一起添加)。一种选择是定义一个从Salary
Int
的函数
Salary
但事实上,如果在声明getSalary :: Salary -> Int
getSalary (Salary sal) = sal
时使用记录语法,Haskell会为您编写这些
newtype
现在你可以写
了data Salary = Salary { getSalary :: Int }
答案 1 :(得分:5)
第1部分:
data Elem = El String | Node String String Resp
type Resp = [Elem]
第2部分:嗯......有点儿。不满意的答案是:你不应该这样做,因为这样做不太安全。更直接的答案是Elem
需要它自己的构造函数,但Resp
很容易定义为上面的类型同义词。但是,我建议
newtype Resp = Resp { getElems :: [Elem] }
这样您就无法将Elem
的随机列表与Resp
混淆。这也为您提供了函数getElems
,因此您不必在单个构造函数上进行尽可能多的模式匹配。 newtype
基本上让我们的Haskell知道它应该在运行时摆脱构造函数的开销,所以没有额外的间接是很好的。