如何使用Fix类型的Functor实例

时间:2017-07-22 16:32:14

标签: haskell functor fixed-point

假设我想要一个非常通用的ListF数据类型:

{-# LANGUAGE GADTs, DataKinds #-}

data ListF :: * -> * -> * where
  Nil  ::           List a b
  Cons :: a -> b -> List a b

现在,我可以将此数据类型与Data.Fix一起使用来构建f-algebra

import qualified Data.Fix as Fx

instance Functor (ListF a :: * -> *) where
  fmap f (Cons x y) = Cons x (f y)
  fmap _ Nil        = Nil

sumOfNums = Fx.cata f (Fx.Fix $ Cons 2 (Fx.Fix $ Cons 3 (Fx.Fix $ Cons 5 (Fx.Fix Nil))))
  where
    f (Cons x y) = x + y
    f Nil        = 0

但我如何使用这种非常通用的数据类型ListF来创建我认为是递归列表的默认Functor实例(映射列表中的每个值)

我想我可以使用Bifunctor(映射第一个值,遍历第二个值),但我不知道它如何与Data.Fix.Fix一起使用?

2 个答案:

答案 0 :(得分:10)

通过获取bifunctor的固定点来构造递归函子非常正确,因为1 + 1 = 2.列表节点结构作为具有2种子结构的容器给出:“elements”和“sublists”。

令人不安的是,我们需要另一个概念Functor(它捕获一个相当具体的函子,尽管它的名字相当普遍),构造一个Functor作为一个固定点。然而,我们可以(作为一个特技表演)转向更为笼统的仿函数概念,它在修复点下关闭

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)

要利用子结构排序的选择,我们需要一种类型级的案例分析。我们无法摆脱类型函数,如

  1. 我们需要部分应用它,而这是不允许的;
  2. 我们需要在运行时稍微告诉我们哪种类型存在。
  3. 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)
    

答案 1 :(得分:2)

  

我想我可以使用Bifunctor(映射第一个值,遍历第二个值),但我不知道它如何与Data.Fix.Fix一起使用?

你的头上钉了一针。

The bifunctors package包含a "Fix-for-bifunctors" type,如下所示:

newtype Fix f a = In { out :: f (Fix f a) a }
只要Fix fFunctor

f就是Bifunctorfmap递归fmap s f的第一个参数并映射第二个参数。

instance Bifunctor f => Functor (Fix f) where
    fmap f = In . bimap (fmap f) f . out

所以你的List示例看起来像这样:

data ListF r a = Nil | Cons r a

type List = Fix ListF

map :: (a -> b) -> List a -> List b
map = fmap