Ответ 1
Это не "неудачное последствие". На самом деле, некоторые могут видеть это как функцию! Типы (+)
и (-)
являются
> :t (+)
(+) :: Num a => a -> a -> a
> :t (-)
(-) :: Num a => a -> a -> a
Важно понимать, что это допустимо для любого типа a
, даже если a
является типом функции. Например, если тип b -> b -> b
имеет экземпляр Num
, вы можете ограничить (+)
на
(+) :: Num (b -> b -> b) => (b -> b -> b) -> (b -> b -> b) -> b -> b -> b
просто установив a = b -> b -> b
. Из-за currying круглые скобки вокруг трех последних b
не нужны (вы могли бы написать их, но они были бы лишними).
Теперь Num b => b -> b -> b
является именно типом (-)
(при условии, что b
сам должен иметь экземпляр Num
), поэтому функция (-)
заполняет первый "слот" (+)
и тип (+) (-)
равен
(+) (-) :: (Num b, Num (b -> b -> b)) -> (b -> b -> b) -> b -> b -> b
что вы наблюдали.
Это ставит вопрос о том, почему, возможно, было бы полезно иметь экземпляр Num
для функций вообще. На самом деле, имеет ли смысл определять экземпляр Num
для функций?
Я утверждаю, что это так! Вы можете определить
instance Num a => Num (r -> a) where
(f + g) r = f r + g r
(f - g) r = f r - g r
(f * g) r = f r * g r
abs f r = abs (f r)
signum f r = signum (f r)
fromInteger n r = fromInteger n
который имеет прекрасный смысл как экземпляр Num
. И на самом деле, это именно тот экземпляр, который вам нужно интерпретировать как выражение e
-
> let e = (+) (-)
> e 3 2 1
4
Буг?!?
Случилось следующее. Поскольку (Num a) => r -> a
является допустимым экземпляром Num
для любого r
, вы можете заменить r
на a -> a
, что показывает, что (Num a) => a -> a -> a
также является допустимым экземпляром Num
. Таким образом, у вас есть
-- Remember that (+) f = \g r -> f r + g r
(+) (-) 3 2 1
= (\g r s -> (-) r s + g r s) 3 2 1 -- definition of (+) on functions
= (\ r s -> (-) r s + 3 r s) 2 1 -- beta reduction
= (\ s -> (-) 2 s + 3 2 s) 1 -- beta reduction
= (-) 2 1 + 3 2 1 -- beta reduction
= (2 - 1) + 3 -- since (3 2) = 3 and (3 1) = 3
= 1 + 3
= 4
Немного запутанный (в частности, убедитесь, что вы понимаете, почему 3 2 = 3
), но не слишком сбивает с толку, как только вы расширили все определения!
Вы попросили вывести тип (+) (-)
, который использует Haskell. Он опирается на идею "унификации" переменных типа. Это происходит примерно так:
- Вы знаете, что
(+) :: Num a => a -> a -> a
и(-) :: Num b => b -> b -> b
(я использую разные буквы, потому что мы захотим их смять). - Если вы поместите
(-)
в первый слот(+)
, вы должны иметьa ~ b -> b -> b
, поэтому комбинированный тип -
(+) (-) :: (Num a, Num b, a ~ b -> b -> b) => (b -> b -> b) -> (b -> b -> b)
- Теперь вы "объединяете"
a
сb -> b -> b
(как указано слева от стрелки жира, знаком~
), который оставляет вас с -
(+) (-) :: (Num (b -> b -> b), Num b) => (b -> b -> b) -> (b -> b -> b)
- Если мы удалим правые скобки (потому что они избыточны) и переименуем
b
вa
, это сигнатура типа, которую указывает Haskell.