【问题标题】:Calculating the type of `map . foldr`计算 `map 的类型。文件夹`
【发布时间】:2020-03-11 05:54:57
【问题描述】:
map :: (a -> b) -> [a] -> [b]
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

找出map . foldr 类型的系统方法是什么?我知道如何为 map foldr 做这件事,但在作曲时会感到困惑。

谢谢!

【问题讨论】:

  • map.foldr 真的很奇怪,我认为你不太可能在“野外”看到它

标签: haskell types function-composition


【解决方案1】:

显然必须有系统的方式,否则Haskell编译器无法进行类型推断。

我们自己可以这样做的一种方法是逐步插入类型:

我们有以下几种:

(.) :: (b -> c) -> (a -> b) -> (a -> c)
map :: (a' -> b') -> [a'] -> [b']
foldr :: Foldable t => (a'' -> b'' -> b'') -> b'' -> t a'' -> b''

请注意,您必须为出现在不同签名中的类型选择不同的名称才能解决此问题。

1.将map 提供给(.)

如果我们提供一个泛型函数f(.),我们会得到以下类型:

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

选择fmap

map :: (a' -> b') -> [a'] -> [b']

等于

map :: (a' -> b') -> ([a'] -> [b'])

由于f 的类型为(b -> c),我们可以得出结论:

b :: (a' -> b')
c :: ([a'] -> [b'])

插入我们推断的类型:

(.) f :: (a -> b) -> (a -> c)
(.) map :: (a -> (a' -> b')) -> (a -> ([a'] -> [b']))

我们可以去掉一些括号:

(.) map :: (a -> (a' -> b')) -> a -> ([a'] -> [b'])
(.) map :: (a -> (a' -> b')) -> a -> [a'] -> [b']
(.) map :: (a -> a' -> b') -> a -> [a'] -> [b']

2。将foldr 提供给(.) map

再次从提供泛型函数g开始:

(.) map :: (a -> a' -> b') -> a -> [a'] -> [b']
(.) map g :: a -> [a'] -> [b']
g :: (a -> a' -> b')

选择gfoldr

foldr :: Foldable t => (a'' -> b'' -> b'') -> b'' -> t a'' -> b''

等于

foldr :: Foldable t => (a'' -> b'' -> b'') -> b'' -> (t a'' -> b'')

由于g 的类型为(a -> a' -> b'),我们可以得出结论:

a :: (a'' -> b'' -> b'')
a' :: b''
b' :: Foldable t => t a'' -> b''

插入我们推断的类型:

(.) map foldr :: a -> [a'] -> [b']
(.) map foldr :: Foldable t => (a'' -> b'' -> b'') -> [b''] -> [t a'' -> b'']

当向 ghci 询问类型时,我们得到的类型相同:

> :t ((.) map foldr)
((.) map foldr) :: Foldable t => (a1 -> a2 -> a2) -> [a2] -> [t a1 -> a2]

【讨论】:

    【解决方案2】:

    map . foldr 实际上是(.) map foldr。将(.) 的类型添加到我们得到的组合中

            foldr :: Foldable t =>           (a -> (r->r)) -> (r -> (t a -> r))
        map :: (i -> j) -> ([i] -> [j])
    (.) ::    (   b     ->      c      ) -> (    d         ->     b            ) -> (d -> c)
    -----------------------------------------------------------------------------------------
    --            4             2                1                3
    -----------------------------------------------------------------------------------------
    (.) map foldr :: Foldable t =>                                                  (d -> c)
        where                        d ~ a -> (r -> r)       -- 1
                                     c ~ [i] -> [j]          -- 2
                                     b ~ r -> (t a -> r)     -- 3
                                       ~ i ->      j         -- 4
                                     -------------------
                                     i ~ r                   -- 5
                                     j ~       t a -> r      -- 6
    

    因此

    map . foldr :: Foldable t => a -> (r -> r) -> [i] -> [j]          -- by 1,2
                ~  Foldable t => a -> (r -> r) -> [r] -> [t a -> r]   -- by 5,6
    

    这里我们使用了application类型推导规则,

         f   :: A -> B
           x :: A
        ---------------
         f x ::      B
    

    (在逻辑上也称为 modus ponens)。

    我们还可以使用 composition 类型的派生规则,它是专用于 (.) 的应用程序规则,或等效于 (>>>) = flip (.)

               g ::      B -> C
         f       :: A -> B
        ------------------------
         f >>> g :: A ->      C
         g  .  f :: A ->      C
    

    为了适应这个模式,我们把类型写下来有点不同,然后立即得到结果

              map ::                                (i ->      j    ) -> ([i] -> [    j   ])
    foldr         :: Foldable t => (a -> (r->r)) -> (r -> (t a -> r))
    ------------------------------------------------------------------------------------
    foldr >>> map :: Foldable t => (a -> (r->r)) ->                       [r] -> [t a -> r]
    map  .  foldr :: Foldable t => (a -> (r->r)) ->                       [r] -> [t a -> r]
    

    这样更直观。

    【讨论】:

      【解决方案3】:

      好吧,与其使用自动方法来推断类型,我认为您可能会对更直观的答案感兴趣:

      我相信你知道,map . foldr 等同于 (\x -> map (foldr x))。让我们开始吧。

      x 的类型应该是什么?好吧,因为它是foldr 的第一个参数,它应该看起来像一个函数,它接受一些值,一些累加器,并返回与累加器相同类型的东西(根据foldr 的定义)。因此:

      x :: (a -> b -> b)
      

      现在我们有了第一个参数的类型,让我们看看其余的。

      一旦应用(foldr x),我们就会返回一个函数,该函数仍然等待初始累加器值,然后等待任何可折叠类型,并返回与累加器相同类型的值(例如,每个累加器的总和)列表中的元素)。

      所以(foldr x)的类型应该是

      Foldable t => b -> t a -> b
      

      好的,但我们还没有完成,现在让我们看看使用 map 会发生什么。

      map 应该首先被赋予一个函数(根据定义)。 (foldr x) 的返回值是这样看的,也就是说,这个 map 的使用考虑到 (b -> t a -> b) 是需要应用到列表的每个元素的函数的类型。

      也许写成(b -> (t a -> b)) 更清楚。因此,这种对 map 的使用认为它被赋予了一个函数,该函数接受一些 b 类型的输入并返回一个函数,该函数本身接受一个可折叠的a 并返回一个b

      好的,我们快到了。现在,map 还需要另一个参数:一个列表,其中的元素与将应用的函数的输入类型相同。因此,由于我们要应用的函数((foldr x) 的结果)采用 b,因此我们对 map 的使用将采用 [b]

      所以现在我们有了:

       (a -> b -> b) -> [b] -> …
      

      我们只是缺少了那个函数组合的输出值的类型,也就是这个具体使用map的输出值的类型。由于与 map 一起应用的函数返回 (t a -> b) 类型的东西,那么我们将返回的东西列表显然将是 [t a -> b] 类型。

      所以最后你有

      Foldable t => (a -> b -> b) -> [b] -> [t a -> b]
      

      作为map . foldr的类型。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-23
        • 1970-01-01
        • 2016-02-25
        • 1970-01-01
        • 2011-08-17
        相关资源
        最近更新 更多