Haskell递归类型

时间:2013-07-15 05:36:42

标签: haskell recursion types

我试图在Haskell中创建一个函数,在BNF和Haskell类型之间的奇怪混合中返回下面说明的Resp类型。

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

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

2 个答案:

答案 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

NEWTYPE

type Name = String type Salary = Int type Reports = [Employee] data Employee = Worker Name Salary | Manager Name Salary Reports 关键字类似于newtype关键字,但它增加了额外的类型安全性。上一段代码的一个问题是,尽管工作者是typeName的组合,但没有什么可以阻止您使用{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知道它应该在运行时摆脱构造函数的开销,所以没有额外的间接是很好的。