Haskell 使用延迟模式从不动点算子计算(无限)树

Haskell 使用延迟模式从不动点算子计算(无限)树,haskell,infinite,coinduction,Haskell,Infinite,Coinduction,这是一个涉及循环绑定和无限数据结构的函数编程难题。有一点背景,所以抓紧点 设置。让我们定义一个表示递归数据类型的数据类型: type Var = String data STerm = SMu Var STerm | SVar Var | SArrow STerm STerm | SBottom | STop deriving (Show) i、 e.t::=μα。t |α| t→ t|⊥ | ⊤。注

这是一个涉及循环绑定和无限数据结构的函数编程难题。有一点背景,所以抓紧点

设置。让我们定义一个表示递归数据类型的数据类型:

type Var = String
data STerm = SMu Var STerm
           | SVar Var
           | SArrow STerm STerm
           | SBottom
           | STop
   deriving (Show)
i、 e.
t::=μα。t |α| t→ t|⊥ | ⊤。注意⊥ 表示没有居民的类型,而⊤ 表示包含所有居民的类型。注意,
(μα.α)=⊥,因为μ是最小不动点运算符

我们可以将递归数据类型解释为无限树,由反复展开
μα产生。t
to
t[α↦ μα.t]
。(有关此过程的正式描述,请参阅)在Haskell中,我们可以定义一种懒惰树,它没有μ-绑定或变量:

data LTerm = LArrow LTerm LTerm
           | LBottom
           | LTop
   deriving (Show)
以及在普通Haskell中,从一个到另一个的转换函数:

convL :: [(Var, LTerm)] -> STerm -> LTerm
convL _ STop    = LTop
convL _ SBottom = LBottom
convL ctx (SArrow t1 t2) = LArrow (convL ctx t1) (convL ctx t2)
convL ctx (SVar v)
    | Just l <- lookup v ctx = l
    | otherwise = error "unbound variable"
convL ctx (SMu v t) = fix (\r -> convL ((v, r) : ctx) t)
使用
D
,我们定义了一个不动点操作符:它表示要构造
a
的值,您可以访问正在构造的
a
,只要您明天才使用它

data Stream a = Cons a (D (Stream a))
instance Functor Stream where
    fmap f = fixD $ \go (Cons x xs) -> Cons (f x) (go <*> xs)
例如,流由我今天拥有的值
a
和我明天必须生成的流
a
组成

data Stream a = Cons a (D (Stream a))
instance Functor Stream where
    fmap f = fixD $ \go (Cons x xs) -> Cons (f x) (go <*> xs)
使用
fixD
,我可以在流上定义一个
map
函数,该函数保证是有效的,因为对
map
的递归调用只用于生成明天需要的值

data Stream a = Cons a (D (Stream a))
instance Functor Stream where
    fmap f = fixD $ \go (Cons x xs) -> Cons (f x) (go <*> xs)
使用
fixD
(不允许非结构递归引用),如何编写函数
conv::STerm->Term
(或
conv::STerm->D Term
)? 一个特别有趣的测试用例是
SMu“x”(SArrow-STop(SMu“y”(SVar“x”))
;结果结构中应该没有底部


更新。我无意中排除了
STerm
上的结构递归,这不是问题的目的;我已经重新编写以删除该限制。

我的直觉是上下文应该只包含延迟的术语。这样,
conv-ctx(SMu“x”t)
将等同于
fixD(\d->conv((x,r):ctx)t)
,就像原始的
convL
一样

如果是这种情况,那么您需要一种在数据结构中包含延迟项的通用方法,而不是只允许它们出现在箭头中:

data Term = Arrow Term Term
          | Bottom
          | Top
          | Next (D Term)
第一次尝试
conv
可以为我们提供:

conv :: [(Var, D Term)] -> STerm -> Term
conv _ STop = Top
conv _ SBottom = SBottom
conv ctx (SArrow t1 t2) = Arrow (conv ctx t1) (conv ctx t2)
conv ctx (SVar v)
  | Just l <- lookup v ctx = Next l
  | otherwise = error "unbound variable"
conv ctx (SMu v t) = fixD (\r -> conv ((x,r):ctx) t)

我不确定这是否正是您想要的,因为
conv[]SMu“x”(SArrow SBottom(SMu“y”(SVar“x”))
在生成的结构中仍然有底部。您想退出的类型是什么?

您打算在
convL
SMu
案例中禁止无限制递归(
fix
),还是在
SArrow
案例中禁止结构递归

我不认为这是一个在
STerm
上没有结构递归的解决方案,因为这样即使在无限
STerm
上,我们也必须有效率,例如:

foldr(\n->SMu(“x”++show n))未定义[0..]--μα。μβ. μγ. μδ. …
要在
STerm
上使用结构递归来实现这一点,技巧似乎是在上下文中存储
任一项(D项)
。当我们通过
箭头
并生成
D
时,我们可以将所有
右侧
s转换为
左侧
s

type Ctx=[(变量,任一项(D项))]
dCtx::Ctx->D Ctx
dCtx=遍历(遍历(fmap左,或纯id))
conv::STerm->Ctx->Term
conv STop(转换停止)\顶部
conv SBottom=底部
conv(SArrow t1 t2)ctx=箭头(fmap(conv t1)(dCtx ctx))(fmap(conv t2)(dCtx ctx))
conv(SVar v)ctx=案例查找v ctx
无->错误“未绑定变量”
只是(左t)->t
刚好(右)->底部
conv(SMu v t)ctx=fixD(\dr->conv t((v,右dr):ctx))

我正在消化你的答案,但你的最后一个问题是打字错误;显式SBottom应该是一个STop。我认为修改后的
术语
不好:这意味着我可以有Next(Next(Next…),它实际上是底部(或者,我们把它放在部分monad中),但conv应该可以是total。SArrow和SMu中的结构递归是可以的。基本上,如果Coq的良好基础检查人员接受它,那就好了。(如果我想说得更精确,我会定义一个TermF函子,这样μa.termfa=Term,然后给你一个基本的递归组合符,但是语法条件似乎很容易理解)。