任何人都可以解释为什么这两个都快乐地编译:
data A a b = A { a :: a, b :: b }
newtype B a = B (A a (B a))
newtype C = C (A Int C)
但我无法通过类型同义词创建类似的递归定义类型?
type B a = A a (B a)
type C = A Int C
虽然显然data B a = A { a :: a, b :: B a }
工作得很好。
有什么方法可以避免在我希望类型递归的地方处理额外的构造函数X吗?我主要通过访问函数来挑选b
,所以我很好,但如果存在一个简单的规避机制,我想知道它。
我应该用什么编译指示来提高专用数据类型C的性能?只是特别的东西?
在A a b
和A c d
之间进行复制的任何聪明技巧,只定义a -> b
和c -> d
映射而不复制记录两次?我担心A
的字段将来会发生变化。模板Haskell也许?
答案 0 :(得分:29)
这与Equi-recursive types与iso-recursive types有关。 Haskell使用iso-recursive类型实现递归类型,这需要程序员在类型递归发生时告诉类型检查器。标记它的方式是使用特定的构造函数,简单的类型同义词不允许您使用。
等递归类型允许编译器推断出递归发生的位置,但它会导致更复杂的类型检查器,并且在一些看似简单的情况下,问题是不可判定的。
如果您想对equi与iso递归类型进行讨论,请查看Benjamin Pierce的优秀Types and Programming Languages
简短回答:因为类型同义词不引入构造函数,而haskell需要构造函数在类型级别显式标记递归,所以不能使用递归类型的同义词。
答案 1 :(得分:15)
我会回答你的第一个问题和第二个问题。
B的类型是无限类型(A a(A a(A a(A a(...)))))
“类型推理引擎”可以设计用于推断和处理无限类型。不幸的是,程序员的许多错误(印刷的或逻辑的)创建的代码无法具有所需的有限类型并且意外地和意外地拥有无限类型。现在编译器拒绝这样的代码,这几乎总是程序员想要的。将其更改为允许无限类型会在编译时创建更难理解的错误(至少与C ++模板一样糟糕),并且在极少数情况下,您可能会在运行时使其编译和执行错误。
有没有办法避免处理额外的构造函数X. 在哪里我想要类型递归?
没有。 Haskell选择允许递归类型仅使用来自data
或newtype
的显式类型构造函数。这些使代码更加冗长,但newtype
应该有很少的运行时惩罚。这是一个设计决定。