关于 Haskell 中类型的推理

Reasoning about types in Haskell

第 995 页的“Haskell 从第一原则编程”的第 16 章有一个练习,用于手动计算 (fmap . fmap) 如何进行类型检查。它建议将每个 fmap 的类型替换为组合运算符类型中的函数类型:

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

T2 fmap :: Functor f => (m -> n) -> f m -> f n

T3 fmap :: Functor g => (x -> y) -> g x -> g y

通过(尝试)将 T2 和 T3 替换为 T1,我得出以下结果:

T4:((m -> n) -> f m -> f n) -> ((x -> y) -> g x -> g y) -> a -> c

此外,它建议检查 (fmap . fmap) 的类型以查看最终类型应该是什么样子。

T5:(fmap . fmap) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)

我无法理解我应该在这里做什么。任何知识渊博的 haskellers 都可以帮助我入门,或者提供类似练习的示例来展示如何手动计算类型吗?

我们循序渐进:

--- fmap . fmap  =  (.) fmap fmap
--- Functor f, g, ... => ..... 

(.)      :: (   b     ->      c    ) -> (a ->  b ) -> a ->     c
    fmap ::  (d -> e) -> f d -> f e
             --------    ----------
(.) fmap ::                             (a ->d->e) -> a -> f d -> f e
                                             ----          ----------
-- then,

(.) fmap      :: (   a     ->  d  ->  e ) -> a   -> f   d   -> f   e
         fmap ::  (b -> c) -> g b -> g c
                  --------    ---    ---
(.) fmap fmap ::                          (b->c) -> f (g b) -> f (g c)
                                          ------      -----      -----

重要的是在每次单独使用类型时一致地重命名所有类型变量,以避免混淆。

我们利用箭头关联在右边的事实,

 A -> B -> C   ~   A -> (B -> C)

类型推断规则是

   f   :: A -> B
     x :: C
   --------------
   f x ::      B    ,  A ~ C

(f :: A -> B) (x :: C) :: B 在类型的等价/统一下 A ~ C 及其包含的所有内容。