【问题标题】:Partially applied function type (a ->) as Functor instance in Haskell部分应用函数类型 (a ->) 作为 Haskell 中的 Functor 实例
【发布时间】:2020-02-20 22:36:29
【问题描述】:

我正在阅读“Haskell 编程”一书(第二版),偶然发现了练习 2,第 12 章,第 2 部分:

instance Functor ((->) a) where
  fmap = TODO

答案在哪里:

instance Functor ((->) a) where
  fmap = (.)

这让我摸不着头脑。我想这在直观层面上对我来说确实有意义(部分应用函数类型 a -> 是函子,而组合是它的 fmap),但我认为一些很好的例子会巩固我对练习的理解。

我想出了这两个:

main = do
    putStrLn . show $ (fmap (+1) (*2)) (5 :: Int)
    putStrLn . show $ (fmap (show) (+1)) 3

我的例子是否正确地说明了这个练习?

fmap 给定两个参数:

  • 部分应用函数(函数)
  • 另一个部分应用的函数(函子)

更新

fmap 给出两个参数:

  • 函数(函数)
  • 另一个函数(函子)

对我来说只是看起来很奇怪,我不确定我的概念是否正确。

我在 SO 上看到了一些类似的问题(例如 this one),其中 this one 几乎是我正在寻找的东西,但并不完全是(我只是在寻找函子的例子,没有别的 - 没有应用程序和没有单子)。

【问题讨论】:

  • 基本上fmap f g 会将g 的结果与f 进行“后处理”。
  • 这两个参数是 not 本身 部分应用 函数。这些只是函数。
  • @WillemVanOnsem 谢谢。对,这些是功能。我想让我感到困惑的是Functor 类所需的类型参数的数量。看起来Functor 只需要来自其实例的一个参数,而(->) 类型需要两个参数。为了解决这个问题,必须部分应用(->),以便只需要一个参数。我的理解正确吗?
  • @PiotrJustyna 是的,这是完全正确的——(更高种类的)类型可以以与函数完全相同的方式部分应用。 [任一] 是部分应用类型构造函数的另一个很好的例子,它形成了 Functor(实际上也是 ApplicativeMonad)。
  • 请注意,有 个类似函子的东西可以作用于类型为 Type -> Type -> Type 的类型。例如,双函子允许您映射两种不同类型的值。通常的例子是(,):bimap f g (x, y) == (f x, g y),其中(,) a 的函子实例只允许您将函数映射到第二个元素(fmap f (x,y) == (x, f y))。还有一些profunctors,有点奇怪,虽然(->)实例比较容易理解,如果很少使用:dimap f g h == g . h . f

标签: haskell functor


【解决方案1】:

实际上没有什么比这更多的了,对于函子ffmap 的实现(已知对于任何可能的f 最多有一个实现)必须具有类型(a -> b) -> f a -> f b,并满足两个函子定律:

fmap id = id
fmap (g . h) = fmap g . fmap h

f 是类型构造函数(->) r - 即当f a 表示r -> a - 那么所需的类型签名是:

(a -> b) -> (r -> a) -> (r -> b)

(最后一对括号是不必要的,但我把它们留在里面是因为它使“模式”更容易看到),很容易看到正是(.) 运算符的签名。

至于这两条定律,很明显,当您写下它们所说的内容时,它们必须遵守。我将通过详细写出所有内容来证明它们:

fmap id = (.) id
        = \g -> id . g
        = \g -> (\a -> id (g a))
        = \g -> (\a -> g a)
        = \g -> g
        = id

fmap (g . h) = (.) (g . h)
             = \g1 -> (g . h) . g1
             = \g1 -> \a -> ((g . h) . g1) a
             = \g1 -> \a -> g (h (g1 a))

(fmap g) . (fmap h) = ((.) g) . ((.) h)
                    = \g1 -> ((.) g) (h . g1)
                    = \g1 -> g . h . g1
                    = \g1 -> \a -> g (h (g1 a))

所以这些也是一样的。

(不要太担心最后的推导 - 通常这些事情似乎很难遵循如何从一行到下一行的逻辑,即使这里它们基本上都使用组合的定义。这实际上只是表达了函数组合是关联的显而易见的众所周知的事实。无论如何,这是一个普遍的结果,除了我相信某些病理类型,如果满足第一个函子定律,那么第二个总是自动满足。)

重要的是:当f 被定义为f a = r -> a 时,组合运算符具有与fmap 相同的类型,并且满足两个函子定律 - 因此组合是一个合法的定义(并且 只有这样的定义)fmapf 创建一个Functor 实例。真的没有什么比这更重要的了,至少在形式上是这样。

【讨论】:

  • 谢谢。这是关于练习(以及一般函子)的一个很好的新视角。
猜你喜欢
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-07
  • 1970-01-01
  • 1970-01-01
  • 2019-06-30
相关资源
最近更新 更多