Почему f = (+) не требует аннотации типа?

Я имею в виду, например,

f :: (Enum a) => a -> a --without this line, there would be an error
f = succ

Это потому, что succ требует, чтобы его параметр был перечислимым (succ :: (Enum a) => a -> a)

но для (+)

f = (+) --ok

Хотя объявление (+) (+) :: (Num a) => a –> a –> a.

Я имею в виду, почему мне не нужно объявлять f как f :: (Num a) => a –> a –> a?

Ответ 1

Из-за дефолта. Num является классом типа "defaultable", что означает, что если вы оставите его без ограничений, компилятор сделает несколько разумных догадок о том, какой тип вы хотели использовать для него. Попробуйте поместить это определение в модуль, а затем запустите

:t f

в ghci; он должен сказать вам (IIRC) f :: Integer -> Integer -> Integer. Компилятор не знал, какой a вы хотели использовать, поэтому он догадался Integer; и так как это сработало, все прошло с этой догадкой.

Почему он не вывел полиморфный тип для f? Из-за ужасного ограничения мономорфизма [1]. Когда компилятор видит

f = (+)

он думает, что "f - это значение", что означает, что ему нужен один (мономорфный) тип. Eta-expand определение

f x = (+) x

и вы получите полиморфный тип

f :: Num a => a -> a -> a

и аналогично, если вы eta-expand свое первое определение

f x = succ x

вам больше не нужна подпись типа.

[1] Фактическое имя из документации GHC!

Ответ 2

Я имею в виду, почему мне не нужно объявлять f как (+) :: (Num a) => a –> a –> a?

Вам нужно это сделать, если вы вообще объявляете подпись f. Но если вы этого не сделаете, компилятор "догадается" сам подписи – в этом случае это не все замечательно, так как оно может просто скопировать и вставить подпись (+). И это именно то, что он будет делать.

... или, по крайней мере, что он должен делать. Это происходит, если вы используете флаг -XNoMonomorphism. В противном случае, страшное ограничение мономорфизма входит, потому что определение f имеет форму ConstantApplicativeForm = Значение; что делает компилятор немой подписи к следующему лучшему неполиморфному типу, который он может найти, а именно Integer -> Integer -> Integer. Чтобы этого не произошло, вы должны фактически предоставить правильную подпись вручную, для всех функций верхнего уровня. Это также предотвращает много путаницы, и многие ошибки становятся менее запутанными.

Ограничение мономорфизма является причиной

f = succ

не будет работать сам по себе: поскольку он также имеет эту форму CAF, компилятор не пытается вывести правильный полиморфный тип, но пытается найти конкретный экземпляр для создания мономорфной сигнатуры. Но в отличие от Num, класс Enum не предлагает экземпляр по умолчанию.

Возможные решения, упорядоченные по предпочтению:

  • Всегда добавлять подписи. Вы действительно должны.
  • Включить -XNoMonomorphismRestriction.
  • Напишите определения функций в форме f a = succ a, f a b = a+b. Поскольку явно указаны аргументы, они не квалифицируются как CAF, поэтому ограничение мономорфизма не будет пинаться.

Ответ 3

Haskell по умолчанию устанавливает ограничения Num на Int или Integer, я забыл, что.