> {-# 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
。请注意induction
和coinduction
是如何双重的。作为归纳和共感数据类型的示例,请查看此仿函数。
> data StringF rec = Nil | Cons Char rec deriving Functor
如果没有递归,Ind StringF
是有限字符串,CoInd StringF
是有限或无限字符串(如果我们使用递归,它们都是有限的或无限的或未定义的字符串)。通常,可以为任何Functor Ind f -> CoInd f
转换f
。例如,我们可以围绕coinductive类型
> 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 f
和wrap
上使用归纳来获取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
也是如此。
我们可以使用coinduction
从CoInd f
和cowrap
生成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)
)),但我想看看它是否可能没有。
观察convert
和convert'
从未直接或间接使用过递归。 Nifty,ain&#tttt!
答案 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(*)这是假设一种严格的语言。在存在懒惰评估的情况下,事情会发生一些变化,但概念和最终答案是相同的。关于源代码中的延迟评估有一些评论。