Мы начнем с перечисления некоторых известных нам типов. (Мы притворяемся, что числа Int
для простоты - это на самом деле не имеет значения.)
testFn :: Int -> Bool -> [Int]
[1,2,3] :: [Int]
True :: 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
.
Об ограничениях: для MonoFoldable [Int]
мало что можно сказать. [Int]
явно является списочным типом с типом элемента Int
, и этого достаточно, чтобы превратить его в MonaFoldable
с Int
в качестве Element
.
для Monoid (Bool -> [Int])
, это немного сложнее. Мы имеем, что любой тип функции A -> B
является моноидом, если B
является моноидом. Это следует, выполняя операцию поточечно. В нашем конкретном случае c мы полагаемся на [Int]
как моноид и получаем:
mempty :: Bool -> [Int]
mempty = \_ -> []
(<>) :: (Bool -> [Int]) -> (Bool -> [Int]) -> (Bool -> [Int])
f <> g = \b -> f b ++ g b