mono-traversable 中的“concatMap”如何能够“拉出”共同的论点?

dim*_*suz 9 haskell traversal

我正在学习 Haskell 并且正在为 Yesod 做一个简单的 DB-seed 程序,当我偶然发现这种我很难理解的行为时:

testFn :: Int -> Bool -> [Int]
testFn a b = if b then replicate 10 a else []
Run Code Online (Sandbox Code Playgroud)

Yesod GHCI 会议:

$ :t concatMap testFn [3]
concatMap testFn [3] :: Bool -> [Int]
$ (concatMap testFn [1,2,3]) True
[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3]
Run Code Online (Sandbox Code Playgroud)

不知何故,它能够从每个映射中“拉”出第二个“Bool”到一个单一的柯里化参数中。

标准基础 Prelude GHCI 会话甚至拒绝编译此表达式:

$ :t concatMap testFn [3]
error:
    • Couldn't match type 'Bool -> [Int]' with '[b]'
      Expected type: Int -> [b]
        Actual type: Int -> Bool -> [Int]
    • Probable cause: 'testFn' is applied to too few arguments
      In the first argument of 'concatMap', namely 'testFn'
      In the expression: concatMap testFn [3]
Run Code Online (Sandbox Code Playgroud)

原来 Yesod 使用具有自己的单通道可遍历concatMap

$ :t concatMap
concatMap
  :: (MonoFoldable mono, Monoid m) =>
     (Element mono -> m) -> mono -> m
Run Code Online (Sandbox Code Playgroud)

在我目前对 Haskell 的理解水平下,我无法弄清楚这里的类型是如何分布的。有人可以向我解释(尽可能多地面向初学者)这个技巧是如何完成的?testFn以上哪部分符合Element mono类型?

chi*_*chi 6

我们首先列出一些我们知道的类型。(我们假装数字是Int为了简单起见——这并不重要。)

testFn :: Int -> Bool -> [Int]
[1,2,3] :: [Int]
True :: Bool
Run Code Online (Sandbox Code Playgroud)

(concatMap testFn [1,2,3]) True与 相同concatMap testFn [1,2,3] True,因此concatMap必须具有匹配所有这些参数的类型:

concatMap :: (Int -> Bool -> [Int]) -> [Int] -> Bool -> ???
Run Code Online (Sandbox Code Playgroud)

???结果类型在哪里。请注意,由于关联规则,->关联到右侧,因此上述键入与:

concatMap :: (Int -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)
Run Code Online (Sandbox Code Playgroud)

让我们在上面写一个通用类型。我正在添加一些空格来标记相似性。

concatMap :: (MonoFoldable mono, Monoid m) =>
             (Element mono -> m              ) -> mono  -> m
concatMap :: (Int          -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)
Run Code Online (Sandbox Code Playgroud)

啊哈!如果我们选择masBool -> [Int]monoas ,我们就会匹配[Int]。如果我们这样做,我们确实满足了约束MonoFoldable mono, Monoid m(见下文),而且我们也有Element mono ~ Int, 所以一切都进行类型检查。

我们推断,???[Int]从的定义m

关于约束:对于MonoFoldable [Int],没什么好说的。[Int]显然是一个带有Int元素类型的类似列表的类型,这足以使它成为一个MonaFoldablewith Intas its Element

对于Monoid (Bool -> [Int]),它有点复杂。我们有任何函数类型A -> B都是幺半群,如果B是幺半群。接下来是以逐点方式执行操作。在我们的特定情况下,我们依赖于[Int]幺半群,我们得到:

mempty :: Bool -> [Int]
mempty = \_ -> []

(<>) :: (Bool -> [Int]) -> (Bool -> [Int]) -> (Bool -> [Int])
f <> g = \b -> f b ++ g b
Run Code Online (Sandbox Code Playgroud)