打开和关闭切换类型递归

时间:2014-07-26 11:54:16

标签: haskell recursion types

我正在研究一个简单的编译器。它从一个复杂的数据结构开始,它是递归的(即,它是一个表达式树),并以完全相同的数据结构结束,但不允许递归。

“没问题”,我心想,“我只是将递归类型设为类型参数,然后为递归和非递归类型定义类型同义词”。

......啊,是的,但事实证明你实际上并不能这样做。你看,类型同义词不允许递归。 >捂脸<

知道如何实现我的目标吗?


这是一个非常简化的例子。假设我有这两种数据类型:

data Type1 = Foo ID | Bar [Type1] | Baz Type1 Type1

data Type2 = Foo ID | Bar [ID] | Baz ID ID

我的计划就是这样做:

data Type r = Foo ID | Bar [r] | Baz r r

type Type1 = Type Type1
type Type2 = Type ID

但显然这实际上并不奏效。

目前看来我的选择是:

  1. 忽略此问题,并且不要尝试在类型系统中强制执行此限制。
  2. 复制整个类型定义(包括重命名所有构造函数)。
  3. 这些都不是真的令人满意。对于上面的小例子,复制类型并不算太糟糕;对于类型定义,您不希望这样做!

2 个答案:

答案 0 :(得分:2)

你可以这样做:

data Type r = Foo ID | Bar [r] | Baz r r

newtype Type1 = Type1 (Type Type1)
newtype Type2 = Type2 (Type ID) -- or type Type2 = Type ID, but this is more consistent

您需要在处理Type1的函数中删除一个额外的类型构造函数,但这不应该是一个主要问题。

更一般地说,您可以定义a fixed-point type constructor并将其用于定义Type1

答案 1 :(得分:2)

这听起来像你可能会使用recursion-schemes。你基本上有两个选择:

  • 使用Fix(或MuNu)创建递归变体,这意味着(un)一直包装其构造函数会产生一些麻烦。

    data TypeF r = FooF ID | BarF [r] | BazF r r
      deriving (Functor)
    
    type Type1 = Fix TypeF
    type Type2 = TypeF ID
    
  • 为递归案例定义您自己的变体,并根据需要实施project / embed

    data Type = Foo ID | Bar [Type] | Baz Type Type
    
    type instance Base Type = TypeF
    
    instance Foldable Type where
        project (Foo ID) = FooF ID
        project (Bar xs) = BarF xs
        project (Baz x y) = BazF x y
    
    instance Unfoldable Type where
        embed (FooF ID) = Foo ID
        embed (BarF xs) = Bar xs
        embed (BazF x y) = Baz x y
    

    类型系列Base提供了两者之间的联系:projectembed见证TypeBase Type Type之间的同构,Base Type Type }等于TypeF Type,展开一级递归。

在这两种情况下FoldableUnfoldable实现了各种* -morphisms,用于在递归和非递归表示之间进行转换。