如何有效地将归纳类型转换为coinductive类型(不递归)?

时间:2015-12-06 22:20:51

标签: haskell recursion functional-programming induction coinduction

> {-# LANGUAGE DeriveFunctor, Rank2Types, ExistentialQuantification #-}

任何归纳类型都是这样定义的

> newtype Ind f = Ind {flipinduct :: forall r. (f r -> r) -> r}
> induction = flip flipinduct

induction的类型为(f a -> a) -> Ind f -> a。这种称为共同诱导的概念是双重的。

> data CoInd f = forall r. Coinduction (r -> f r) r
> coinduction = Coinduction

coinduction的类型为(a -> f a) -> a -> CoInd f。请注意inductioncoinduction是如何双重的。作为归纳和共感数据类型的示例,请查看此仿函数。

> data StringF rec = Nil | Cons Char rec deriving Functor

如果没有递归,Ind StringF是有限字符串,CoInd StringF是有限或无限字符串(如果我们使用递归,它们都是有限的或无限的或未定义的字符串)。通常,可以为任何Functor Ind f -> CoInd f转换f。例如,我们可以围绕coinductive类型

包装一个functor值
> wrap :: (Functor f) => f (CoInd f) -> CoInd f
> wrap fc = coinduction igo Nothing where
>   --igo :: Maybe (CoInd f) -> f (Maybe (CoInd f))
>   igo Nothing  = fmap Just fc
>   igo (Just (Coinduction step seed)) = fmap (Just . Coinduction step) $ step seed

此操作为每个步骤添加额外操作(模式匹配Maybe)。这意味着它会产生O(n)开销。

我们可以在Ind fwrap上使用归纳来获取CoInd f

> convert :: (Functor f) => Ind f -> CoInd f
> convert = induction wrap

这是O(n^2)。 (获取第一个图层的是O(1),但由于嵌套的O(n),第n个元素为Maybe,因此总共O(n^2)。) 双重地,我们可以定义cowrap,它采用归纳类型,并显示其顶级Functor层。

> cowrap :: (Functor f) => Ind f -> f (Ind f)
> cowrap = induction igo where
>    --igo :: f (f (Ind f)) -> f (Ind f)
>    igo = fmap (\fInd -> Ind $ \fold -> fold $ fmap (`flipinduct` fold) fInd)

induction始终为O(n)cowrap也是如此。

我们可以使用coinductionCoInd fcowrap生成Ind f

> convert' :: (Functor f) => Ind f -> CoInd f
> convert' = coinduction cowrap

每当我们获得一个元素时,这又是O(n),总共O(n^2)

我的问题是,如果不使用递归(直接或间接),我们可以在Ind f时间内将CoInd f转换为O(n)吗?

我知道如何使用递归(将Ind f转换为Fix f然后将Fix f转换为CoInd f(初始转换为O(n),但随后CoInd f中的每个元素都是O(1),第二次转换O(n)总计,O(n) + O(n) = O(n))),但我想看看它是否可能没有。

观察convertconvert'从未直接或间接使用过递归。 Nifty,ain&#tttt!

1 个答案:

答案 0 :(得分:1)

是的,这是正式可能的:

https://github.com/jyp/ControlledFusion/blob/master/Control/FixPoints.hs

但是,转换仍然需要构建一个中间缓冲区,这只能在运行时使用循环来完成。

这种限制的根本原因是'归纳'类型的值对给定的评估顺序(*)作出响应,而'归纳'类型的值 >修复评估顺序。在不强制进行许多重新计算的情况下实现转换的唯一方法是分配某种中间缓冲区,以便记忆中间结果。

顺便说一句,从'co-inductive'到'inductive'的转换不需要缓冲区,但需要通过使用显式循环来重新评估评估顺序。

顺便说一下,我在两篇论文中研究了基本概念: 1.在Haskell中,对于有效的流:https://gist.github.com/jyp/fadd6e8a2a0aa98ae94d 2.在经典线性逻辑中,用于数组和流。 http://lopezjuan.com/limestone/vectorcomp.pdf

(*)这是假设一种严格的语言。在存在懒惰评估的情况下,事情会发生一些变化,但概念和最终答案是相同的。关于源代码中的延迟评估有一些评论。