我正在学习 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类型?
我们首先列出一些我们知道的类型。(我们假装数字是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)