函数的类型类实例

Typeclass instances for functions

我刚刚意识到,Functions 有 Monad、Functor 和 Applicative 的实例。

当我看到一些我没有得到的类型类实例时,我通常做的是写一些类型良好的表达式并看看它是什么 returns:

有人可以解释这些情况吗?您通常听说过 List 和 Maybe 的实例,这对我来说很自然,但我不明白 Functions 怎么可以是 Functor 甚至 Monad。

编辑: 好的,这是一个有效的类型良好的表达式,但无法编译:

fmap (+) (+) 1 (+1) 1

首先,我同意你的看法:函数不像函子那样直观,事实上我有时希望这些实例不存在。并不是说它们有时没有用,而是它们经常以不必要和令人困惑的方式使用。这些方法总是可以替换为更具体的组合器(特别是来自 Control.Arrow),或者替换为等效但不知何故更具描述性的 reader monad.

也就是说...要理解函数函子,我建议你先考虑Map。在某种程度上,Map Int 很像一个数组:它包含一些你可以转换的元素(即 fmap over),你可以通过 indexing[=61 访问单个元素=] 他们与整数。 Map 只允许“数组”中有间隙,并从整数索引推广到任何可以排序的索引。

但从另一个角度来看,Map 只是 函数 的特定实现:它将参数(键)与结果(值)相关联。这应该很清楚 函数仿函数 是如何工作的:它 fmaps 函数的所有可能结果

不幸的是,这个解释并没有很好地解释 Monad 实例,因为 Map 实际上没有 monad(甚至 Applicative)实例。 list/array 实现的直接改编确实是不可能的……回顾一下:在列表中,我们有

pure x ≡ [x]
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(a,y),(a,z),(b,x),(b,y),(b,z)]

所以合并后,索引都是不同的。这不适用于 Map 我们想要支持通用键的地方。

但是,列表有一个替代的 monad 实例,zip 列表:

pure x ≡ repeat x
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(b,y)]

请注意,元素的索引已保留。

现在这个实例实际上可以适用于 Map,只要有一个 repeat :: a -> Map k a 生成器。这是不存在的,因为通常有无限多的键,我们不能枚举它们,也不能平衡这样一个 Map 需要的无限树。但是,如果我们将自己限制在只有有限多个可能值的键类型(例如 Bool),那么我们很好:

instance Applicative (Map Bool) where
  pure x = Map.fromList [(False, x), (True, x)]
  <*> = Map.intersectWith ($)

现在,这正是函数 monad 的工作方式,与 Map 不同的是,如果可能有无限多个不同的参数,则没有问题,因为您永远不会尝试存储 所有这些参数 具有关联值;相反,您总是只在现场计算值。


如果不是懒惰地完成那将是不可行的——这在 Haskell 中几乎不是问题,事实上如果你 fmap在 Map 上它也懒惰地发生。对于function functor,fmap其实不只是偷懒,结果也立马忘记了,需要重新计算

fmap 对于函数作用于函数产生的结果:

GHCi> :set -XTypeApplications
GHCi> :t fmap @((->) _)
fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b

t -> a函数的a结果通过a -> b函数修改。如果这听起来很像函数组合,那是因为它正是:

GHCi> fmap (3 *) (1 +) 4
15
GHCi> ((3 *) <$> (1 +)) 4
15
GHCi> ((3 *) . (1 +)) 4
15

(<*>) 有点棘手:

GHCi> :t (<*>) @((->) _)
(<*>) @((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b

Applicative f => f (a -> b) 参数变为 t -> (a -> b)(<*>) 通过使用辅助函数(t -> a 类型)将两个参数(类型t -> a -> b)的函数转换为一个参数(类型t -> b)的函数从第一个参数生成第二个参数:

GHCi> :t \k f -> \x -> k x (f x)
\k f -> \x -> k x (f x) :: (t2 -> t -> t1) -> (t2 -> t) -> t2 -> t1

这是一个以应用风格编写的示例,使用了函数的 FunctorApplicative 实例:

GHCi> ((&&) <$> (> 0) <*> (< 4)) 2
True

一种阅读方式是"feed 2 to (> 0) and to (< 4), and combine the results with (&&)"。它可以用 Control.Applicative 中的 liftA2 以更紧凑的方式编写,但我相信 (<$>)/(<*>) 拼写更能揭示意图。

Applicative的另一种方法,pure...

GHCi> :t pure @((->) _)
pure @((->) _) :: a -> t -> a

... 从 a 中创建一个 t -> a 函数,仅此而已。常数函数是唯一的方法:

GHCi> pure 2 "foo"
2
GHCi> pure 2 42
2

请注意 pure 2 在上面的每个示例中都有不同的类型。

综上所述,Monad 实例出奇地无趣。为了更加清楚,让我们看一下 (=<<) 而不是 (>>=):

GHCi> :t (=<<) @((->) _)
(=<<) @((->) _) :: (a -> t -> b) -> (t -> a) -> t -> b

如果将此类型与 (<*>) 中的类型进行比较,您会发现它们是相同的,只是第一个参数被翻转了。函数实例是一种例外情况,其中 ApplicativeMonad 做的事情本质上是相同的。

值得一提的是,Control.Monad中的join可用于将一个值用作双参数函数的两个参数:

GHCi> :t join @((->) _)
join @((->) _) :: (t -> t -> a) -> t -> a
GHCi> join (*) 5
25