对 (fmap length Just) [1,1,1,1] 与 fmap length $ Just [1,1,1,1] 感到困惑

Confused about (fmap length Just) [1,1,1,1] vs. fmap length $ Just [1,1,1,1]

我知道括号会强制执行不同的操作顺序,但我不太理解第一个结果:

>> (fmap length Just) [1, 2, 3]
1

鉴于以下内容非常合理 - 我们将长度函数提升到 Just 结构上,所以我们应该得到 "Just [length of list]":

>> fmap length $ Just [1, 2, 3]
Just 3

第一种情况是怎么回事?

在第一种情况下,您正在获取 Functor 的函数实例,为此 fmap = (.),因此:

fmap length Just [1,2,3]
=
(length . Just) [1,2,3]
=
length (Just [1,2,3])

MaybeFoldable 实例表示 Nothing 的长度为 0Just 的长度为 1 -- 如果您认为 Maybe a 有点像最多包含一个 aa 的集合,这是非常明智的。


长话短说
  1. fmap 只能应用于两个参数,所以首先让我们主要关注 fmap length Just
  2. 函数上的fmap实例是(.)即函数组合,所以表达式基本上是(length . Just) [1,2,3]
  3. 上面的表达式等价于length (Just [1,2,3])
  4. 请注意,我们将 length 应用于 Maybe 值,这取决于 Maybe 的 Foldable 实例。 Foldable 是类型构造函数的类型类,而不是类型(就像 Functor),因此根据类型构造函数采用的参数类型(即 aMaybe a)。列表 [1, 2, 3] 只是一个干扰,因为 lenght 只能查询最外层,所以表达式与 length (Just ()).
  5. 相同
  6. 一个 Maybe 值只能包含 0 或 1 个值,而 Just 包含 1。所以答案是 1

除了丹尼尔所说的,我想详细解释一下我们是如何遇到这个问题的。

给定的表达式是fmap length Just [1,2,3],可以进一步写成((fmap length) Just) [1,2,3]

让我们看看我们拥有的所有三个函数的类型定义

> :t fmap
 fmap :: Functor f => (a -> b) -> f a -> f b

> :t length
 length :: Foldable t => t a -> Int

> :t Just
 Just :: a -> Maybe a

现在让我们将类型代入 fmap 看看我们得到什么。

fmap :: (a    ->  b) ->  f a      -> f b
         t a     Int ->  f (t a)  -> f Int

用 ghci 确认

> :t fmap length
fmap length :: (Functor f, Foldable t) => f (t a) -> f Int

可以肯定地说 Functions 也是 Functors稍后会详细介绍)。

这意味着我们可以说上面的f -> a(t a)Maybe a这没关系,因为Maybe有一个可折叠的实例定义. IE f (t a) 可以写成 (-> a) (Maybe a) 可以写成 a -> Maybe aJust.

的类型

类似f Int可以写成 (-> a) Int类似于 a -> Int

因此 fmap lenght Just 的类型定义是

> :t fmap length Just
fmap length Just :: a -> Int

但这并没有完全消除我的困惑,现在我知道我们得到了 Int,但为什么呢?有件事让我很担心。看看上面的类型,唯一可能的方法是如果 Just 的值以某种方式被输入到长度中。

好的!将数据输入另一个函数让我想起了一些事情。啊哈!它类似于 Function composition,我的意思是如果我们有这样的东西:

fmap length Just [1,2,3] == (length . Just) [1,2,3] == (length (Just [1,2,3])

这或许可以解释这一点。但是为什么我们要在这里编写函数。这里有些东西不符合要求。

看起来两个函数的fmap相当于它们的组合。这可能吗?


让我们go-ahead解开这个疑惑。我们需要问自己两个问题:

  1. Function 可以是 Functor 的实例吗?
  2. 如果函数是 Functor 的实例,那么它们的 fmap 实现是什么?

  1. Function 可以是 Functor 的实例吗?

稍微挖掘一下,我发现是的——Function 可以是仿函数的一个实例。任何函数都可以写成 a -> b,也可以进一步写成 (->) a b,因为->只不过是一个接受两个参数的类型构造函数。

现在我们知道 Functors 可以接受 * -> * 类型,但是 (->) 有一种我们不能使用的 * -> * -> * 类型,但是..那 (->) a呢,它有一种* -> *,我们可以用它来做一个Functor的实例。


  1. 如果函数是 Functor 的实例,那么它们的 fmap 实现是什么?

现在我们知道函数可以是 Functor 的一个实例,是时候实现它们了。挖掘 into the source,我确实找到了 (->)

的仿函数实例
instance  Functor ((->) r) where
    fmap = (.)

让我们看看我们是如何实现的:

fmap的类型是:

fmap :: (a  ->  b) ->  f a -> f b

(->) a 代替 f 因为,函数也可以是函子:

fmap :: (a  ->  b) ->  f a          -> f b
                       ( (->) r a)  -> f ( (->) r b) 

可以简化为:

fmap :: (a  ->  b) ->  f a        -> f b
                      ( r -> a)  -> ( r -> b) 

所以以函数作为仿函数的 fmap 的最终类型定义是:

fmap :: (a  ->  b) -> ( r -> a) -> (r -> b)

啊哈!如果我们仔细观察,我们看到的是,它看起来完全像 function composition (.) 的类型,其中 r -> a 的输出作为第一个参数 a -> b 的输入提供给我们结果 (r -> b)

也就是说,我们可以放心地说

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

因此这证明了这一点,当我们将 fmap 作为函子放在函数上时,fmap 的实例就是函数组合。

回顾我们的问题:

((fmap length) Just) [1,2,3]

这可以看作:

fmap length Just [1,2,3] == (length . Just) [1,2,3] == (length (Just [1,2,3])

答案:

> (length (Just [1,2,3]) == 1

A Maybe 可以包含两个值 01,并且由于 Just 包含一些东西,我们得到 Just a 作为 1