为什么Functor类没有返回函数?

23

从范畴论的角度来看,函子是一对映射(一个在对象之间,另一个在范畴的箭头之间),遵循一些公理。

我假设每个函子实例都类似于数学定义,即可以映射对象和函数,但Haskell的Functor类只有将函数映射的fmap函数。

为什么呢?

更新:换句话说:

每种Monad类型M都有一个函数return :: a -> M a

而Functor类型F没有函数return :: a -> F a,只有F x构造函数。


2
FunctorMonad类的情况完全相反”是什么意思?由于单子确实是函子,所以它不能相反。至于“为什么范畴论论证适用于Haskell类型理论”:类型理论与此毫无关系。这只是_Haskell标准库_,它们实现了模拟范畴论概念的类型类。 - leftaroundabout
1
确切地说,return 代表自然变换 _η_:1 → _T_,它适用于每个单子但不适用于一般的函子。那么...你还有什么疑问吗? - leftaroundabout
1
@leftaroundabout 是的,这正是我没有理解的地方。现在一切都清楚了,只是SO已经锁定。 - uhbif19
在“实例 Functor f where ...”中,“f”是一个接受类型并给出另一个类型的东西(因此它是类型之间的映射)。例如,Maybe是一个类型构造器,它接受一个类型并给出一个类型。如果你给它Int,那么它会给你Maybe Int。fmap将f a值映射到f b值,给定一个函数a -> b。 - David Young
@DavidYoung "fmap将函数a -> b应用于f a值,返回f b值。" 我对其解释不太清楚,也与数学定义无关。为什么把fmap看作Hask箭头的映射比直接使用map更好呢? - uhbif19
显示剩余4条评论
5个回答

16
有两个层次:类型和值。由于Hask的对象是类型,因此您只能使用类型构造函数对它们进行映射,这些函数具有* -> *种类:
  • α -> F α(对于Functor F),
  • β -> M β(对于Monad M)。
然后,对于一个函子,您需要对态射(即值的函数)进行映射:
fmap :: (α -> β) -> (F α -> F β)

重要的是,Monadreturn :: α -> M α 不是你可能认为的类型 αM α 的映射器。根据单子的数学定义,return 对应于从 Id 函子到 M 函子的自然变换。而且在 Hask 中,Id 函子有点隐含。单子的标准定义还需要另一个自然变换 M ◦ M -> M。因此,在 Haskell 中进行翻译如下:

class Functor m => Monad m where
    return :: Id α -> m α
    join :: m (m α) -> m α

作为旁注:这两个自然变换实际上是单位和乘法,使单子成为“自函子范畴中的幺半群”。
实际定义有所不同,但是是等效的。请参阅Haskell / wiki
如果您使用从标准绑定>>= :: m α -> (α -> m β) -> m β派生的类似于组合的运算符:
(>=>) :: Monad m => (α -> m β) -> (β -> m γ) -> (α -> m γ)
f >=> g = \a => f a >>= g

你可以看到,这实际上都与Kleisli类别有关。另请参阅nLab上的文章,了解计算机科学中的单子。

8

一个范畴的对象与面向对象(OO)编程语言中的对象不同(在Haskell中我们更倾向于将它们称为;关于它们在范畴论中的含义,可以参考这里)。相反,在Hask中的对象是类型FunctorHask中是自函子(endofunctors),即通过以下方式将类型与类型关联:

Prelude> :k Maybe
Maybe :: * -> *
Prelude> :k Int
Int :: *
Prelude> :k Maybe Int
Maybe Int :: *

另一方面,Hask中的箭头实际上是一些函数类型a -> b的值。它们是以下方式关联的:

fmap :: ( Functor (f ::   t     ->     f t       {- type-level  -} ) )
             =>         (a->b)  ->  fmap(a->b)   {- value-level -}
                     ≡  (a->b)  ->  (f a->f b)

是的,那绝对就是我所说的。Functor由两个态射组成,在Haskell中,其中一个只是函数,另一个是类型级实体。 - uhbif19
5
那并不完全正确。如果您希望将functors视为“morphisms”,则只有一个:在范畴论中,对象是范畴,介于类别_A_和类别_B_之间的“morphisms” 是把_A_的对象映射到_B_的对象以及把_A_的“morphisms”映射到_B_的“morphisms”的函子。在这个例子中,“Maybe”是Hask的自函子,也就是范畴论中的自同态,正如我所说,它将对象映射到对象(例如将类型“Int”映射到类型“Maybe Int”),将“morphisms”映射到“morphisms”(例如将函数“length::String->Int”映射到“fmap length :: Maybe String -> Maybe Int”)。 - leftaroundabout

7
虽然您在问题中使用了那些花哨的分类术语,并且可能已经对现有答案感到非常满意,但以下是一种相对简单的解释的尝试:
假设Functor类型类中存在一个叫做return(或pure、unit或...)的函数。
现在尝试定义一些常见的Functor实例:[](列表)、Maybe和((,) a)(带有左组件的元组)。
很容易,是吧?
这里是普通Functor实例:
instance Functor [] where
   fmap f (x : xs) = f x : fmap xs
   fmap _ []       = []

instance Functor Maybe where
   fmap f (Just x) = Just (f x)
   fmap _ Nothing  = Nothing

instance Functor ((,) a) where
   fmap f (x, y) = (x, f y)

现在对于Functor,return怎么处理呢?
列表:
instance Functor [] where
   return x = [x]

好的。也许呢?
instance Functor Maybe where
   return x = Just x

好的,现在是元组:
instance Functor ((,) a) where
   return x = (??? , x)

你看,我们不知道应该将哪个值填入元组的左部分。实例声明中指定了它的类型为a,但我们并不知道这个类型中的值。也许类型a是只有一个值的Unit类型。但如果它是Bool类型,我们应该选择True还是False?如果它是Either Int Bool类型,我们应该选择Left 0Right False还是Left 1
因此,如果Functor中有return语句,通常情况下就不能定义很多有效的functor实例(你需要强制使用类似于FunctorEmpty的类型类约束)。
如果您查看FunctorMonad的文档,您会发现确实存在Functor ((,) a)的实例,但不存在Monad ((,) a)的实例。这是因为您无法为该实例定义return

3
非常好的补充!谢谢。 - uhbif19

4
如果你有
instance Functor F where
    fmap = ...

那么类型构造函数 F 是作用于对象(即类型)的操作,它将类型 T 映射到类型 F T,而 fmap 是作用于态射(即函数)的操作,它将函数 f :: T -> U 映射到 fmap f :: F T -> F U


但是为什么没有像单子那样的特殊函数呢? - uhbif19
你是什么意思?单子的情况也不例外。 - Tom Ellis
每个Monad类型M都有一个函数return :: a -> M a。但是,函子类型F没有函数return :: a -> F a。 - uhbif19
那并不是将对象映射到对象。用范畴论的术语来说,它是一个态射。 - Tom Ellis

0
在范畴论中,函子将一个范畴中的所有对象映射到另一个范畴中,但函子不会映射对象中的元素。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接