通过取双函子的固定点来构造递归函子是完全正确的,因为 1 + 1 = 2。列表节点结构作为具有 2 种子结构的容器给出:“元素”和“子列表”。
令人不安的是,我们需要一个完全不同的概念Functor
(它捕获了相当特定种类的函子,尽管它的名称相当笼统)来构造 aFunctor
作为定点。然而,我们可以(作为一个噱头)转向一个稍微更一般的函子概念,它在固定点下是封闭的。
type p -:> q = forall i. p i -> q i
class FunctorIx (f :: (i -> *) -> (o -> *)) where
mapIx :: (p -:> q) -> f p -:> f q
这些是索引集上的函子,因此这些名称不仅仅是对 Goscinny 和 Uderzo 的无偿敬意。您可以将其o
视为“某种结构”和i
“某种子结构”。这是一个示例,基于 1 + 1 = 2 的事实。
data ListF :: (Either () () -> *) -> (() -> *) where
Nil :: ListF p '()
Cons :: p (Left '()) -> p (Right '()) -> ListF p '()
instance FunctorIx ListF where
mapIx f Nil = Nil
mapIx f (Cons a b) = Cons (f a) (f b)
为了利用子结构排序的选择,我们需要一种类型级别的案例分析。我们无法摆脱类型函数,因为
- 我们需要它被部分应用,这是不允许的;
- 我们需要在运行时告诉我们存在哪种排序。
data Case :: (i -> *) -> (j -> *) -> (Either i j -> *) where
CaseL :: p i -> Case p q (Left i)
CaseR :: q j -> Case p q (Right j)
caseMap :: (p -:> p') -> (q -:> q') -> Case p q -:> Case p' q'
caseMap f g (CaseL p) = CaseL (f p)
caseMap f g (CaseR q) = CaseR (g q)
现在我们可以获取固定点:
data Mu :: ((Either i j -> *) -> (j -> *)) ->
((i -> *) -> (j -> *)) where
In :: f (Case p (Mu f p)) j -> Mu f p j
在每个子结构位置,我们进行案例拆分,看看我们应该有一个 -p
元素还是一个Mu f p
子结构。我们得到了它的功能性。
instance FunctorIx f => FunctorIx (Mu f) where
mapIx f (In fpr) = In (mapIx (caseMap f (mapIx f)) fpr)
要从这些东西构建列表,我们需要在 和 之间进行*
权衡() -> *
。
newtype K a i = K {unK :: a}
type List a = Mu ListF (K a) '()
pattern NilP :: List a
pattern NilP = In Nil
pattern ConsP :: a -> List a -> List a
pattern ConsP a as = In (Cons (CaseL (K a)) (CaseR as))
现在,对于列表,我们得到
map' :: (a -> b) -> List a -> List b
map' f = mapIx (K . f . unK)