什么是双函子?
What are bifunctors?
我对 Haskell 比较陌生,无法理解双函子的实用性。我想我在理论上理解它们:例如,如果我想映射一个抽象多个具体类型的类型,例如 Either 或 Maybe,我需要将它们封装在一个双函子中。但一方面,这些示例似乎特别做作,另一方面,您似乎确实可以通过组合简单地实现相同的功能。
例如,我在 Jeremy Gibbons 和 Bruno C.d. 的 The Essence of the Iterator Pattern 中看到了这段代码。 S.奥利维拉:
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 组合函数有什么不同。我想我显然一定遗漏了一些东西,无论是在这个例子中还是在一般情况下。
我觉得你有点想多了。双函子就像一个双参数函子。 Gibbons 和 Oliveira 的想法只是双函子的一种应用,就像递归方案的标准动物园只是函子的一种应用。
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
Bifunctor
s 有一种 * -> * -> *
并且两个参数都可以协变映射。将此与常规 Functor
s 进行比较,后者只有一个参数 (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
实例允许您仅映射第二个组件,但 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)
使用标准函子 Fix
,正如我在上面所说的,List
没有 Functor
实例的泛型推导,因为 Fix
不知道关于 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
当然,如果你想通用地处理具有多个参数的递归结构,那么你需要泛化到三函子、四函子等……这显然是不可持续的,需要做大量的工作(在更高级的编程语言中)已被用于设计更灵活的系统来表征类型。
我对 Haskell 比较陌生,无法理解双函子的实用性。我想我在理论上理解它们:例如,如果我想映射一个抽象多个具体类型的类型,例如 Either 或 Maybe,我需要将它们封装在一个双函子中。但一方面,这些示例似乎特别做作,另一方面,您似乎确实可以通过组合简单地实现相同的功能。
例如,我在 Jeremy Gibbons 和 Bruno C.d. 的 The Essence of the Iterator Pattern 中看到了这段代码。 S.奥利维拉:
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 组合函数有什么不同。我想我显然一定遗漏了一些东西,无论是在这个例子中还是在一般情况下。
我觉得你有点想多了。双函子就像一个双参数函子。 Gibbons 和 Oliveira 的想法只是双函子的一种应用,就像递归方案的标准动物园只是函子的一种应用。
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
Bifunctor
s 有一种 * -> * -> *
并且两个参数都可以协变映射。将此与常规 Functor
s 进行比较,后者只有一个参数 (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
实例允许您仅映射第二个组件,但 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)
使用标准函子 Fix
,正如我在上面所说的,List
没有 Functor
实例的泛型推导,因为 Fix
不知道关于 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
当然,如果你想通用地处理具有多个参数的递归结构,那么你需要泛化到三函子、四函子等……这显然是不可持续的,需要做大量的工作(在更高级的编程语言中)已被用于设计更灵活的系统来表征类型。