[我正在学习Haskell,并在偶然发现这种行为时发现它是为Yesod做的一个简单的DB种子程序,我很难理解:
testFn :: Int -> Bool -> [Int]
testFn a b = if b then replicate 10 a else []
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]
以某种方式,它能够从每个映射中“拉出”第二个“布尔”成为一个单独的咖喱参数。
[标准基本前奏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]
结果Yesod使用具有自己的mono-traversable的concatMap
库:
$ :t concatMap
concatMap
:: (MonoFoldable mono, Monoid m) =>
(Element mono -> m) -> mono -> m
以我目前对Haskell的理解水平,我无法弄清楚这里的类型如何分布。有人可以向我(尽可能多地面向初学者)解释这个技巧是如何完成的吗?上面testFn
的哪一部分符合Element mono
类型?
我们首先列出一些我们知道的类型。 (为简单起见,我们假装数字为Int
-这并不是很重要。)
testFn :: Int -> Bool -> [Int]
[1,2,3] :: [Int]
Bool
[(concatMap testFn [1,2,3]) True
与concatMap testFn [1,2,3] True
相同,因此concatMap
必须具有与所有这些参数匹配的类型:
concatMap :: (Int -> Bool -> [Int]) -> [Int] -> Bool -> ???
其中???
是结果类型。注意,由于关联规则,->
关联在右侧,因此上述键入与以下内容相同:
concatMap :: (Int -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)
让我们在那上面写一个通用类型。我要添加一些空格来标记相似之处。
concatMap :: (MonoFoldable mono, Monoid m) =>
(Element mono -> m ) -> mono -> m
concatMap :: (Int -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)
啊哈!如果选择m
作为Bool -> [Int]
,选择mono
作为[Int]
,则我们有一个匹配项。如果这样做,我们确实满足约束MonoFoldable mono, Monoid m
,并且我们也有Element mono ~ Int
,因此所有类型检查。
我们从???
的定义推断[Int]
是m
。