通过组合仿函数可以实现哪些分支?

时间:2016-12-10 09:03:59

标签: haskell functor bifunctor

我对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编写函数有什么不同。我认为我显然必须遗漏一些东西,无论是在这个例子中,还是在一般情况下。

1 个答案:

答案 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 FixFunctor的{​​{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

当然,如果你想通常使用带有多个参数的递归结构,那么你需要推广到三个仿函数,四元函数等......这显然是不可持续的,并且有很多工作(在更高级的编程语言已被用于设计更灵活的系统来表征类型。