我觉得你有点想多了。双函子就像一个双参数函子。Gibbons 和 Oliveira 的想法只是双函子的一种应用,就像递归方案的标准动物园只是函子的一种应用一样。
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
Bifunctor
s 有一种,* -> * -> *
两个参数都可以协变映射。将此与常规Functor
s 进行比较,后者只有一个f :: * -> *
可以协变映射的参数 ( )。
例如,想想Either
's 通常的Functor
例子。它只允许您fmap
覆盖第二个类型参数 -Right
值被映射,Left
值保持原样。
instance Functor (Either a) where
fmap f (Left x) = Left x
fmap f (Right y) = Right (f y)
但是,它的Bifunctor
实例允许您映射总和的两半。
instance Bifunctor Either where
bimap f g (Left x) = Left (f x)
bimap f g (Right y) = Right (g y)
同样对于元组:(,)
的Functor
实例只允许您映射第二个组件,但Bifunctor
允许您映射两个部分。
instance Functor ((,) a) where
fmap f (x, y) = (x, f y)
instance Bifunctor (,) where
bimap f g (x, y) = (f x, g y)
请注意Maybe
,您提到的 不适合双函子的框架,因为它只有一个参数。
关于 的问题Fix
,双函子的不动点允许您表征具有函子类型参数的递归类型,例如大多数类似容器的结构。让我们以列表为例。
newtype Fix f = Fix { unFix :: f (Fix f) }
data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)
如上所述,使用标准 functorial Fix
,没有Functor
for实例的通用派生List
,因为对's参数Fix
一无所知。也就是说,我不能写任何东西,因为有错误的种类。我必须手动启动一个for 列表,也许使用:List
a
instance Something f => Functor (Fix f)
Fix
map
cata
map :: (a -> b) -> List a -> List b
map f = cata phi
where phi Nil_ = Fix Nil_
phi Cons_ x r = Fix $ Cons_ (f x) r
的双函数版本Fix
确实允许 的实例Functor
。Fix
使用双函子的一个参数插入递归出现的Fix f a
,另一个代表结果数据类型的函子参数。
newtype Fix f a = Fix { unFix :: f a (Fix f a) }
instance Bifunctor f => Functor (Fix f) where
fmap f = Fix . bimap f (fmap f) . unFix
所以我们可以写:
deriveBifunctor ''ListF
type List = Fix ListF
并免费获取Functor
实例:
map :: (a -> b) -> List a -> List b
map = fmap
当然,如果你想通用地处理具有多个参数的递归结构,那么你需要推广到三仿函数、四仿函数等......这显然是不可持续的,而且还有大量工作(在更高级的编程中)语言)已被用于设计更灵活的系统来表征类型。