当谈到将类别理论应用于泛型编程时,Haskell做得非常好,例如像recursion-schemes
这样的库。然而,我不确定的一件事是如何为多态类型创建通用仿函数实例。
如果你有一个多态类型,比如List或Tree,你可以创建一个从(Hask×Hask)到Hask的仿函数代表它们。例如:
data ListF a b = NilF | ConsF a b -- L(A,B) = 1+A×B
data TreeF a b = EmptyF | NodeF a b b -- T(A,B) = 1+A×B×B
这些类型在A上是多态的,但是关于B是固定点,如下所示:
newtype Fix f = Fix { unFix :: f (Fix f) }
type List a = Fix (ListF a)
type Tree a = Fix (TreeF a)
但是大多数人都知道,列表和树也是通常意义上的仿函数,它们代表a
的“容器”,您可以映射函数f :: a -> b
以获取容器b
的。{/ p>
我试图找出是否有办法以通用的方式将这些类型(固定点)作为Functor
的实例,但我不确定如何。到目前为止,我遇到了以下两个问题:
1)首先,必须有一种方法可以在任何多态固定点上定义泛型gmap
。知道像ListF
和TreeF
这样的类型是Bifunctors,到目前为止我已经得到了这个:
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Bifunctor
newtype Fix f = Fix { unFix :: f (Fix f) }
cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = f . fmap (cata f) . unFix
-- To explicitly use inF as the initial algebra
inF :: f (Fix f) -> Fix f
inF = Fix
gmap :: forall a b f. Bifunctor f => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = cata alg
where
alg :: f a (Fix (f b)) -> Fix (f b)
alg = inF . bimap f id
在Haskell中,这给了我以下错误:Could not deduce (Functor (f a)) arising from a use of cata from the context (Bifunctor f)
。
我正在使用bifunctors
包,该包具有WrappedBifunctor
类型,专门定义可解决上述问题的以下实例:Bifunctor p => Functor (WrappedBifunctor p a)
。但是,我不确定如何在Fix
内“提升”此类型以便能够使用它
2)即使可以定义上面的通用gmap
,我也不知道是否可以创建Functor
的{{1}}通用实例并且可以立即用于那里的fmap = gmap
和List
类型(以及以类似方式定义的任何其他类型)。这可能吗?
如果是这样,是否可以使其与Tree
兼容?
答案 0 :(得分:6)
如果您现在愿意接受与bifunctors打交道的那一刻,您可以说
cata :: Bifunctor f => (f a r -> r) -> Fix (f a) -> r
cata f = f . bimap id (cata f) . unFix
然后
gmap :: forall a b f. Bifunctor f => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = cata alg
where
alg :: f a (Fix (f b)) -> Fix (f b)
alg = inF . bimap f id
(在gmap
中,我刚重新安排了您的类约束,以使作用域类型变量有效。)
您也可以使用原始版本的cata
,但是您需要同时使用。{1}}
Functor
上的Bifunctor
和gmap
约束:
gmap :: forall a b f. (Bifunctor f, Functor (f a)) => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = cata alg
where
alg :: f a (Fix (f b)) -> Fix (f b)
alg = inF . bimap f id
您无法使gmap
成为正常Functor
类的实例,因为它需要类似
instance ... => Functor (\ x -> Fix (f x))
我们没有类型级别的lambda。如果您反转f
的两个参数,可以执行此操作,但是您会丢失"其他" Functor
个实例,需要再次为cata
定义Bifunctor
。
[您可能还有兴趣阅读http://www.andres-loeh.de/IndexedFunctors/以获得更一般的方法。]
答案 1 :(得分:5)
TBH我不确定这个解决方案对你有多大帮助,因为它仍然需要为这些定点仿函数额外newtype
包装,但是我们走了:
cata
给出以下两个辅助函数:
unwrapFixBifunctor :: (Bifunctor f) => Fix (WrappedBifunctor f a) -> Fix (f a)
unwrapFixBifunctor = Fix . unwrapBifunctor . fmap unwrapFixBifunctor . unFix
wrapFixBifunctor :: (Bifunctor f) => Fix (f a) -> Fix (WrappedBifunctor f a)
wrapFixBifunctor = Fix . fmap wrapFixBifunctor . WrapBifunctor . unFix
您可以在gmap
上定义f
而无需任何其他约束:
gmap :: (Bifunctor f) => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = unwrapFixBifunctor . cata alg . wrapFixBifunctor
where
alg = inF . bimap f id
Fix . f
Functor
变为newtype
我们可以通过将此“类型级lambda”实现为Functor
来为\a -> Fix (f a)
实现newtype
实例:
newtype FixF f a = FixF{ unFixF :: Fix (f a) }
instance (Bifunctor f) => Functor (FixF f) where
fmap f = FixF . gmap f . unFixF
答案 2 :(得分:0)
bifunctors
包还提供了Fix
的特别合适的版本:
newtype Fix p a = In {out :: p (Fix p a) a}
这很容易成为Functor
个实例:
instance Bifunctor p => Functor (Fix p) where
fmap f = In . bimap (fmap f) f . out