将 compose 应用到 f​​map

Applying compose to fmap

几个星期以来,我一直在努力弄清楚 Haskell 编译器如何将 (.) 应用于 fmap。

我的意思是

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

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

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

编译器是如何得出 (.) fmap 的类型的?

我本来想在这里问这个问题的,但是当我解释我试过的东西时,一切都在一起了。所以现在我也要 post 回答。

为了得到这个,我使用了 fmap

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

如果

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

然后

(.)函数开头的

(b -> c)可以用

代替
((a -> b) -> (f a -> f b))

因此我有

((a1 -> b) -> (f a1 -> f b)) -> (a -> (a1 -> b)) -> a -> (f a1 -> f b)

因为(.)已经应用到fmap,我们可以去掉((a1 -> b) -> (f a1 -> f b)),剩下

(a -> (a1 -> b)) -> a -> (f a1 -> f b)

为了更加简洁,我们可以去掉多余的括号。

来自 IRC Beginner-haskell 频道的 Glguy 和 Hamme 都提醒我 (->) 是右联想

例如(a -> b -> c -> d) = (a -> (b -> (c -> d)))

所以我们去掉多余的括号。

(a -> a1 -> b) -> a -> f a1 -> f b

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

a重命名为c,将a1重命名为a,再多加一对括号,可以直观理解类型签名:

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

第一个参数是一个函数,returns 另一个函数 (a -> b) 被输入 fmap。应用第一个参数会生成等待该参数 c 的完整组合函数。应用 c 产生 fmap (a -> b) ,它只等待最后一个参数 f a.

   ((.) fmap)
   ((.) fmap (c -> (a -> b))     -- Apply the 1st argument
   ((.) fmap (c -> (a -> b)) c   -- Apply the 2nd argument
   fmap (a -> b)
   fmap (a -> b) f a             -- Apply the 3rd argument
   f b                           -- The result

一个例子:

> ((.) fmap) (\n -> (+n)) 42 [1..5]  -- Becomes: fmap (+42) [1..5]
[43,44,45,46,47]
> ((.) fmap) (\n -> (+n)) 13 [1..5]
[14,15,16,17,18]

了解类型如何派生的一种方法是查看 (fmap .) 的含义。

思考fmap . g:这是什么意思?扩展 . 的定义,我们看到 fmap . g = \x -> fmap (g x)。由于 fmap 的第一个参数需要是类型为 a -> b 的函数,因此 g 必须是类型为 c -> a -> b 的函数;它计算一个给定参数的适当函数。

现在,虽然我们可以将 fmap f 直接应用于列表(或其他仿函数),但我们需要先给 fmap . g 一个参数:

fmap f someFunctorialValue == someOtherFunctorialValue
((fmap . g) x) someFunctorialValue == someOtherFunctorialValue

去掉一些多余的括号,这就变成了

(fmap .) g x someFunctorialValue == someOtherFunctorialValue

现在我们可以直接知道每个表达式的类型应该是什么了:

-- someFunctorialValue :: Functor f => f a
-- someOtherFunctorialValue :: Functor f => f b
-- x :: c
-- g :: (c -> a -> b)
-- (fmap .) :: (c -> a -> b) -> c -> f a -> f b
-- fmap     :: (     a -> b)      -> f a -> f b

换句话说:fmap接受具体函数a -> b,而(fmap .)接受"parameterized"函数g和"function selector" x.