(Num a) vs Вывод целочисленного типа
Рассмотрим следующую функцию Haskell:
sign a
| a < 0 = (-1)
| a > 0 = 1
| otherwise = 0
Когда я загружаю это в ghci, я ожидал, что :t sign
будет:
sign :: (Num a, Ord a) => a -> Integer
Вместо этого он сделал вывод, что:
*Main> :t sign
sign :: (Num a1, Num a, Ord a1) => a1 -> a
Аналогично, если я задаю тип целого 5
, я ожидал Integer
, но вместо этого я получил
*Main> :t 5
5 :: Num a => a
Там что-то я не понимаю о типах Haskell. Дело в том, что если все, что я знаю о типе return sign
, это то, что он является экземпляром класса Num
, тогда я не смог бы передать его возвращаемое значение в эту функцию:
double :: Integer -> Integer
double x = x * 2
То есть для моей функции double
требуется Integer
, а не только любой экземпляр Num
.
Тем не менее, следующее работает очень хорошо:
*Main> double (sign 5.5)
2
Что я плохо понимаю о системе типа Haskell?
Ответы
Ответ 1
Дело в том, что если все, что я знаю о возвращаемом типе "знака", это то, что он является экземпляром класса Num
, тогда я не смог бы передать его возвращаемое значение в эту функцию:
Правильно, если бы это было все, что вы знали, вы не смогли бы передать его на double
.
Но тип
sign :: (Num a1, Num a, Ord a1) => a1 -> a
означает, что тип результата sign
зависит от того, какой тип Num
задает запрос вызывающего абонента. Типовые переменные в типах подписей (неявно) универсально количественно, а не экзистенциально, например, например. Интерфейсы Java.
sign
может выдавать возвращаемое значение произвольного типа, при условии, что это ограничение является экземпляром Num
, а возвращаемый им тип определяется вызывающим контекстом.
Если вызывающий абонент хочет Integer
, он получает его. Если он хочет double
, не проблема.
Я забыл упомянуть изначально:
Аналогично, если я попрошу тип целого числа 5, я ожидал "Integer", но вместо этого получил
*Main> :t 5
5 :: Num a => a
Числовые литералы являются полиморфными, целочисленный литерал обозначает fromInteger value
и дробный литерал для fromRational value
.
Ответ 2
Я просто хотел немного разъяснить @DanielFischer ответ. Подпись типа f :: Num b => a -> b
означает, что f
способен возвращать любой экземпляр класса Num
. Когда вызывается f
, Haskell использует контекст (подпись типа вызывающего) для определения конкретного типа b
.
Более того, числовые литералы Haskell являются примером такого типа полиморфизма. Вот почему :t 5
дал вам Num a => a
. Символ 5
способен действовать как любой тип числа, а не только целое число. Контекст, в котором он появляется, определяет, какой он будет.
Ответ 3
В Haskell, если функция возвращает тип x
- это его результат, это означает, что вызывающий может выбрать то, что x
должно быть, а не функция. Скорее, функция должна иметь возможность возвращать любой возможный тип.
Ваш sign
может возвращать любые типы данных, включая Integer
. Функция double
требует Integer
, так что просто отлично - sign
может вернуть это.
Еще одна часть загадки, о которой вы не можете знать: на Java 2
имеет тип int
и 2.0
имеет тип double
. Но в Haskell 2
имеет тип Num x => x
- другими словами, любой возможный тип номера. (Также 2.0
имеет тип Fractional x => x
, что является аналогичной сделкой.)