两个Functor组合是什么意思?

29

Haskell Typeclassopedia Section 3.2的第5题要求证明或反例以下语句:

两个Functor的组合也是一个Functor。

起初,我以为它是在谈论将两个不同实例的Functorfmap方法进行组合,但这似乎不合理,因为就我所知,类型无法匹配。对于两种类型ff'fmap的类型分别为fmap :: (a -> b) -> f a -> f bfmap :: (a -> b) -> f' a -> f' b,这并不真正可组合。那么什么是将两个Functor组合呢?


3
你尝试过在ghci中实际组合fmap本身吗?即:t fmap . fmap - Squidly
2
@MrBones 感谢您的提示!对于那些没有ghci访问权限的人,输出是::(Functor f1,Functor f)=>(a-> b)-> f(f1 a)-> f(f1 b) - akbiggs
4个回答

31

一个 Functor 提供了两种映射:一种是在类型级别上将类型映射到类型(这是 instance Functor x where 中的 x),另一种是在计算级别上将函数映射到函数(这是 fmap = x 中的 x)。你可能正在考虑组合计算级别的映射,但应该考虑组合类型级别的映射;例如,给定

newtype Compose f g x = Compose (f (g x))

你能写吗?

instance (Functor f, Functor g) => Functor (Compose f g)

? 如果不是,则为什么不是?


3
这是一个优秀的答案,解释了Typeclassopedia练习的内容而没有泄露答案。 - Will

19

这里谈论的是类别构造函数的组合,例如[]Maybe,而不是函数的组合,如fmap。因此,例如,有两种方式可以组合[]Maybe

newtype ListOfMabye a = ListOfMaybe [Maybe a]
newtype MaybeOfList a = MaybeOfList (Maybe [a])

两个 Functors 的组合是一个 Functor 的说法意味着有一种公式化的方式来为这些类型编写 Functor 实例:

instance Functor ListOfMaybe where
    fmap f (ListOfMaybe x) = ListOfMaybe (fmap (fmap f) x) 

instance Functor MaybeOfList where
    fmap f (MaybeOfList x) = MaybeOfList (fmap (fmap f) x)

事实上,Haskell平台附带了模块Data.Functor.Compose,它提供了一个Compose类型,可以免费完成这个操作:

import Data.Functor.Compose

newtype Compose f g a = Compose { getCompose :: f (g a) }

instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = Compose (fmap (fmap f) x)

Compose 特别适用于使用 GeneralizedNewtypeDeriving 扩展:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype ListOfMaybe a = ListOfMaybe (Compose [] Maybe a)
   -- Now we can derive Functor and Applicative instances based on those of Compose
   deriving (Functor, Applicative)

请注意,两个Applicative的组合也是一个Applicative。因此,既然[]Maybe都是Applicative,那么Compose [] MaybeListOfMaybe也是Applicative。组合Applicative是一种非常巧妙的技术,这种技术正在逐渐变得更加常见,作为单子变换器的替代方案,用于在不需要单子的全部能力的情况下。


2
这里的所有答案中,无疑最实用。让我进一步问你:当你写fmap f (Compose x)时,x的类型是什么?我会说它是f g a,但我仍然很难想象计算过程(fmap(fmap f)x)。必须更加努力地思考getCompose。 注:我理解字母"f"在这里扮演了两种不同的角色。 - Marco Faustinelli
1
@MarcoFaustinelli 你可以通过查看与之匹配的Compose构造函数来确定它的类型。由于 newtype Compose f g a = Compose (f (g a)),因此当你对其进行模式匹配时,你会得到一个类型为 f (g a) 的值。 - amalloy
1
如果它被定义为 newtype Compose f g a = MkCompose (f (g a)),那么它将是 fmap foo (MkCompose x) = MkCompose (fmap (fmap foo) x) - Will Ness

14

思考范畴论解释有助于理解,一个函子 F: C -> D将来自类别 C 的对象(值)和态射(函数)映射到来自类别 D 的对象和态射。

对于第二个函子 G: D -> E, 函子的复合 G . F : C -> E 就是将 F 的终域 fmap 转换为 G 的定义域 fmap。在 Haskell 中,这可以通过一些新类型拆包来实现。

import Data.Functor

newtype Comp f g a = Comp { unComp :: f (g a) }

compose :: f (g a) -> Comp f g a
compose = Comp

decompose :: Comp f g a -> f (g a)
decompose = unComp

instance (Functor f, Functor g) => Functor (Comp f g) where
  fmap foo = compose . fmap (fmap foo) . decompose

1
f 同时指代 functorfunction 吗?如果是,为什么 Functor f 的类型约束不适用于 fmap f 中的 f - Kamel

13

两个函数的组成是将一个函数放在另一个函数中,例如:

round (sqrt 23)

这是两个函数roundsqrt的组合。同样,如果你把一个函子放在另一个函子里面,就可以得到两个函子的组合,例如

Just [3, 5, 6, 2]

List 是一个函子,Maybe 也是一个函子。如果您试图弄清楚 fmap 应该对上面的值做什么,那么您就可以获得一些关于它们的组合也是函子的直觉。当然,它应该在内部函子的内容上执行映射操作!


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