【问题标题】:What does it mean to compose two Functors?组合两个 Functor 是什么意思?
【发布时间】:2013-11-15 11:24:44
【问题描述】:

Exercise 5 of the Haskell Typeclassopedia Section 3.2 要求对陈述进行证明或反例

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

起初我以为这是在讨论由 Functor 的两个独立实例定义的 fmap 方法,但这并没有真正意义,因为就我而言,类型不会匹配可以告诉。对于ff' 两种类型,fmap 的类型将是fmap :: (a -> b) -> f a -> f bfmap :: (a -> b) -> f' a -> f' b,这似乎不是可组合的。那么组成两个Functors是什么意思呢?

【问题讨论】:

  • 您是否尝试过在 ghci 中实际组合 fmap ?即:t fmap . fmap
  • @MrBones 感谢您的提示!对于那些没有 ghci 访问权限的人,输出为 :: (Functor f1, Functor f) => (a -> b) -> f (f1 a) -> f (f1 b)

标签: haskell theory functor


【解决方案1】:

Functor 提供两种映射:一种在类型级别将类型映射到类型(这是instance Functor x where 中的x),另一种是在计算级别将函数映射到函数(这是xfmap = x)。您正在考虑编写计算级映射,但应该考虑编写类型级映射;例如,给定

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

你会写吗

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

?如果没有,为什么不呢?

【讨论】:

  • 这是一个很好的答案,它解释了 Typeclassopedia 练习而不给出解决方案。
【解决方案2】:

这里说的是[]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)

请注意,两个Applicatives 的组合也是一个Applicative。因此,由于[]MaybeApplicatives,所以Compose [] MaybeListOfMaybe 也是。编写Applicatives 是一种非常巧妙的技术,如今它正慢慢变得越来越普遍,在您不需要 monad 的全部功能的情况下,它可以替代 monad 转换器。

【讨论】:

  • 肯定是这里所有答案中最有用的。让我进一步问您:当您编写 fmap f (Compose x) 时,x 的类型是什么?我会说它是 f g a,但我仍然很难将计算可视化(fmap (fmap f) x)。必须更加努力地考虑 getCompose。注意:我知道字母“f”在这里用于两个不同的角色。
  • @MarcoFaustinelli 您可以通过查看您要匹配的 Compose 的构造函数来判断它是什么类型。因为newtype Compose f g a = Compose (f (g a)),所以当你对它进行模式匹配时,你会得到一个f (g a)类型的值。
  • @MarcoFaustinelli 如果它被定义为newtype Compose f g a = MkCompose (f (g a)),那它就是fmap foo (MkCompose x) = MkCompose (fmap (fmap foo) x)
【解决方案3】:

两个函数的组合就是你把一个函数放在另一个函数里面,比如

round (sqrt 23)

这是roundsqrt这两个函数的组合。同样,两个仿函数的组合是当你将一个仿函数放在另一个仿函数中时,例如

Just [3, 5, 6, 2]

List 是一个函子,Maybe 也是。如果您尝试弄清楚 fmap 应该对上述值做什么,您可以对为什么它们的组合也是函子有一些直觉。当然它应该映射到内部函子的内容!

【讨论】:

    【解决方案4】:

    在这里考虑分类解释真的很有帮助,函子F: C -> D 将对象(值)和态射(函数)从C 类别中的对象和态射带到D 类别中的对象和态射。

    对于第二个仿函数 G : D -> E,仿函数 G . F : C -> E 的组合只是将 F fmap 变换的域作为 G fmap 变换的域。在 Haskell 中,这是通过一个小的 newtype 展开来完成的。

    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
    

    【讨论】:

    • f 是否同时指代functorfunction?如果答案是肯定的,那么为什么Functor f 的类型约束不适用于fmap f 中的f
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 2021-03-09
    • 1970-01-01
    相关资源
    最近更新 更多