> {-# 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
是一个有限或无限字符串(如果我们使用递归,它们都是有限或无限或未定义的字符串)。一般来说,可以转换Ind f -> CoInd f
为任何 Functor f
。例如,我们可以将函子值包裹在一个协约类型周围
> 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)
,但第 n 个元素是O(n)
由于嵌套Maybe
的 s 造成的,因此是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'
从不直接或间接使用递归。漂亮,不是吗!