Вы на самом деле немного неправильно сообщили тип. Там есть некоторые важные ограничения класса типов:
(+)(+2) :: (Num a, Num (a -> a)) => (a -> a) -> a -> a
Так откуда это? Это на самом деле довольно просто. Во-первых, сигнатура типа (+)
равна
(+) :: Num a => a -> a -> a
Или переписать, чтобы сделать карри явным:
(+) :: Num a => a -> (a -> a)
Между тем, тип (+2)
(который является результатом выполнения именно этого частичного приложения):
(+2) :: Num a => a -> a
Теперь, когда вы делаете (+)(+2)
, вы (частично) применяете функцию (+)
к функции (+2)
. То есть мы рассматриваем (+2)
как первый аргумент (+)
. Чтобы это работало, его тип - Num a => a -> a
- должен быть экземпляром Num
. Вот почему у нас есть еще одно ограничение типа: a -> a
должен быть экземпляром Num
. (Это никогда не происходит по умолчанию, но вы можете определить свой собственный экземпляр для числовых функций - обычно он применяет обе функции для ввода и добавляет результаты.)
Итак, давайте специализируем сигнатуру типа (+)
для случая, когда она применяется к функции (a -> a)
(которая, как я только что сказал, сама должна быть экземпляром Num
, а также a
сам). Мы получаем:
(+) :: (Num a, Num (a -> a)) => (a -> a) -> (a -> a) -> (a -> a)
или с указанием карри:
(+) :: (Num a, Num (a -> a)) => (a -> a) -> ((a -> a) -> (a -> a))
То есть он принимает функцию a -> a
и возвращает «функцию высшего порядка» типа (a -> a) -> (a -> a)
. Таким образом, когда мы применяем это к (+2)
, мы получаем именно такую функцию высшего порядка:
(+)(+2) :: (Num a, Num (a -> a)) => (a -> a) -> (a -> a)
Это именно то, что сообщается, так как последняя пара скобок не нужна. (Это из-за карри снова.)
Второй случай полностью аналогичен, за исключением того, что функция, к которой вы применяете, является a -> a -> a
, а не a -> a
.