我对Haskell相对较新,并且无法理解bifunctors的效用。我认为我理论上理解它们:例如,如果我想映射抽象多种具体类型的类型,例如Either或Maybe,我需要将它们封装在一个bifunctor中。但一方面,这些例子似乎特别人为,另一方面,你似乎只能通过构图实现相同的功能。
作为一个例子,我在杰里米吉本斯和布鲁诺C. d的The Essence of the Iterator Pattern中看到了这段代码。 S. Oliveira:
import Data.Bifunctor
data Fix s a = In {out::s a (Fix s a) }
map' :: Bifunctor s => (a -> b) -> Fix s a -> Fix s b
map' f = In . bimap f (map' f) . out
fold' :: Bifunctor s => (s a b -> b) -> Fix s a -> b
fold' f = f . bimap id (fold' f) . out
unfold' :: Bifunctor s => (b -> s a b) -> b -> Fix s a
unfold' f = In . bimap id (unfold' f) . f
我理解的重点是组合映射和折叠函数来创建迭代器模式,这是通过定义需要两个参数的数据构造函数来实现的。但实际上我并不了解这与使用常规仿函数和用fmap而不是bimap编写函数有什么不同。我认为我显然必须遗漏一些东西,无论是在这个例子中,还是在一般情况下。
答案 0 :(得分:11)
我认为你有点过分思考。 bifunctor就像一个双参数函子。 Gibbons和Oliveira的想法只是一个bifunctors的应用,就像标准的递归方案动物园只是一个仿函数的应用程序。
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
Bifunctor
有一种* -> * -> *
,两个参数都可以进行协变映射。将其与常规Functor
进行比较,常规f :: * -> *
只有一个参数(Either
),可以进行协变映射。
例如,考虑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
的{{1}}实例允许您仅映射第二个组件,但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
不适合bifunctors的框架,因为它只有一个参数。
在Fix
的问题上,bifunctor的固定点允许您表征具有functorial类型参数的递归类型,例如大多数类似容器的结构。我们以列表为例。
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
的{{1}}实例没有通用推导,因为List
对此一无所知Fix
的{{1}}参数。也就是说,我不能写List
之类的内容,因为a
有错误的类型。我必须手动使用instance Something f => Functor (Fix f)
列表,可能使用Fix
:
map
cata
的bifunctorial版本允许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
使用其中一个bifunctor参数来插入递归出现的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
当然,如果你想通常使用带有多个参数的递归结构,那么你需要推广到三个仿函数,四元函数等......这显然是不可持续的,并且有很多工作(在更高级的编程语言已被用于设计更灵活的系统来表征类型。