为什么`(fmap.fmap)中的类型总和只是[1,2,3]`工作?

jav*_*nor 6 haskell

我正在享受我生命中的时间从最初的原则阅读精彩的Haskell编程,并且我通过以下示例来看,我只是无法拆开(Page 1286电子阅读器):

Prelude> (fmap . fmap) sum Just [1, 2, 3]
Just 6
Run Code Online (Sandbox Code Playgroud)

对我来说很明显以下是如何工作的:

Prelude> fmap sum $ Just [1,2,3]
Just 6
Run Code Online (Sandbox Code Playgroud)

我已经手动解构,(fmap . fmap)以了解这些类型是如何工作的.但是当把它想象为"两次提升"时,它没有意义,因为我正在解除Just和List数据构造函数.

我输入以下内容ghci:

Prelude> :t (fmap . fmap)
(fmap . fmap)
  :: (Functor f, Functor f1) => (a -> b) -> f1 (f a) -> f1 (f b)

Prelude> :t (fmap . fmap) sum
(fmap . fmap) sum
  :: (Num b, Foldable t, Functor f, Functor f1) =>
     f1 (f (t b)) -> f1 (f b)

Prelude> :t (fmap . fmap) sum Just
(fmap . fmap) sum Just :: (Num b, Foldable t) => t b -> Maybe b
Run Code Online (Sandbox Code Playgroud)

我不明白如何派生最后的输出.喂奶时(fmap . fmap) sumJust数据构造函数,编译器如何知道更换两f1fMaybe?在这里得到一个好的答案后,我怎么能自己弄明白呢?

Li-*_*Xia 7

这并没有解除两者MaybeList(也就是说(fmap . fmap) sum (Just [1,2,3]),它有一个类型问题),但在功能类型(->)Maybe.

Just :: a -> Maybe a
     -- ((->) a) (Maybe a)
     -- f (g a)   for f ~ ((->) a)  and  g ~ Maybe

(fmap . fmap) :: (a   -> b) -> f (g a  ) -> f (g b)
     -- Num x => ([x] -> x) -> f (g [x]) -> f (g x)
     -- Num x => ([x] -> x) -> ([x] -> Maybe [x]) -> [x] -> Maybe x
     --          ^             ^                     ^
     --          sum           Just                  [1,2,3]
Run Code Online (Sandbox Code Playgroud)


Die*_*Epp 7

如果您不理解特定答案的工作原理,请将您提供的参数与上一步中的类型对齐.

Prelude> :t (fmap . fmap) sum
(fmap . fmap) sum
  :: (Functor f, Functor f1, Num b) => f (f1 [b]) -> f (f1 b)
Run Code Online (Sandbox Code Playgroud)

所以为了这项工作,Just必须有类型f (f1 [b]),然后(fmap . fmap) sum Just必须有类型f (f1 b).

Just :: (Functor f, Functor f1, Num b) => f (f1 [b])
Run Code Online (Sandbox Code Playgroud)

这不是明显发生了什么ff1应该是在这里,所以让我们尝试RHS来代替.我们可以作弊并要求GHCi检查实际价值(fmap . fmap) sum Just应该是多少:

Prelude> :t (fmap . fmap) sum Just
(fmap . fmap) sum Just :: Num b => [b] -> Maybe b
Run Code Online (Sandbox Code Playgroud)

但这应该匹配:

(Functor f, Functor f1, Num b) => f (f1 b)
Run Code Online (Sandbox Code Playgroud)

我们试图找出ff1在这里.所以我们必须稍微重写一下,以便它具有相同的结构(记住这->是语法糖并且有时会妨碍):

(fmap . fmap) sum Just :: Num b => [b] -> Maybe b
-- Same as...
(fmap . fmap) sum Just :: Num b => (->) [b] (Maybe b)
-- Or...
(fmap . fmap) sum Just :: Num b => ((->) [b]) (Maybe b)
--                     Functor f = ((->) [b])
--                                Functor f1 = Maybe
Run Code Online (Sandbox Code Playgroud)

所以我们可以弄清楚,为了匹配类型,Functor f 必须(->) [b]......记住函数也是函子!而Functor f1Maybe,这是比较明显的一点.

我们可以测试一下:

Prelude> :t (fmap . fmap) sum :: Num b => ([b] -> Maybe [b]) -> ([b] -> Maybe b)
(fmap . fmap) sum :: Num b => ([b] -> Maybe [b]) -> ([b] -> Maybe b)
  :: Num b => ([b] -> Maybe [b]) -> [b] -> Maybe b
Run Code Online (Sandbox Code Playgroud)

而GHCi认为它的类型检查就好了.

这里唯一容易忘记的部分就是这(->) [b]是一个有效的函子!