ghci> minBound :: Int
–2147483648
ghci> maxBound :: Char
'\1114111'
ghci> maxBound :: Bool
True
ghci> minBound :: Bool
False
Функции minBound
и maxBound
интересны тем, что имеют тип (Bounded a) => a
. В этом смысле они являются полиморфными константами.
Все кортежи также являются частью класса Bounded
, если их компоненты принадлежат классу Bounded
.
ghci> maxBound :: (Bool, Int, Char)
(True,2147483647,'\1114111')
Класс Num
Класс Num
– это класс типов для чисел. Его экземпляры могут вести себя как числа. Давайте проверим тип некоторого числа:
ghci> :t 20
20 :: (Num t) => t
Похоже, что все числа также являются полиморфными константами. Они могут вести себя как любой тип, являющийся экземпляром класса Num
(Int
, Integer
, Float
или Double
).
ghci> 20 :: Int
20
ghci> 20 :: Integer
20
ghci> 20 :: Float
20.0
ghci> 20 :: Double
20.0
Если проверить тип оператора *
, можно увидеть, что он принимает любые числа.
ghci> :t (*)
(*) :: (Num a) => a –> a –> a
Он принимает два числа одинакового типа и возвращает число этого же типа. Именно поэтому (5 :: Int) * (6 :: Integer)
приведёт к ошибке, а 5 * (6 :: Integer)
будет работать нормально и вернёт значение типа Integer
потому, что 5 может вести себя и как Integer
, и как Int
.
Чтобы присоединиться к классу Num
, тип должен «подружиться» с классами Show
и Eq
.
Класс Floating
Класс Floating
включает в себя только числа с плавающей точкой, то есть типы Float
и Double
.
Функции, которые принимают и возвращают значения, являющиеся экземплярами класса Floating
, требуют, чтобы эти значения могли быть представлены в виде числа с плавающей точкой для выполнения осмысленных вычислений. Некоторые примеры: функции sin
, cos
и sqrt
.
Класс Integral
Класс Integral
– тоже числовой класс типов. Если класс Num
включает в себя все типы, в том числе действительные и целые числа, то в класс Integral
входят только целые числа. Для типов Int
и Integer
определены экземпляры данного класса.
Очень полезной функцией для работы с числами является fromIntegral
. Вот её объявление типа:
fromIntegral :: (Num b, Integral a) => a –> b
Из этой сигнатуры мы видим, что функция принимает целое число (Integral)
и превращает его как более общее число (Num)
.
ПРИМЕЧАНИЕ. Необходимо отметить, что функция fromIntegral
имеет несколько ограничений классов в своей сигнатуре. Такое вполне допустимо – несколько ограничений разделяются запятыми и заключаются в круглые скобки.
Это окажется полезно, когда потребуется, чтобы целые числа и числа с плавающей точкой могли «сработаться» вместе. Например, функция вычисления длины length
имеет объявление length :: [a] –> Int
, вместо того чтобы использовать более общий тип (Num b) => length :: [a] –> b
. (Наверное, так сложилось исторически – хотя, по-моему, какова бы ни была причина, это довольно глупо.) В любом случае, если мы попробуем вычислить длину списка и добавить к ней 3.2
, то получим ошибку, потому что мы попытались сложить значения типа Int
и число с плавающей точкой. В этом случае можно использовать функцию fromIntegral
:
ghci> fromIntegral (length [1,2,3,4]) + 3.2
7.2
Несколько заключительных слов о классах типов
Поскольку класс типа определяет абстрактный интерфейс, один и тот же тип данных может иметь экземпляры для различных классов, а для одного и того же класса могут быть определены экземпляры различных типов. Например, тип Char
имеет экземпляры для многих классов, два из которых – Eq
и Ord
, поскольку мы можем сравнивать символы на равенство и располагать их в алфавитном порядке.
Иногда для типа данных должен быть определён экземпляр некоторого класса для того, чтобы имелась возможность определить для него экземпляр другого класса. Например, для определения экземпляра класса Ord
необходимо предварительно иметь экземпляр класса Eq
. Другими словами, наличие экземпляра класса Eq
является Ord
. Если поразмыслить, это вполне логично: раз уж допускается расположение неких значений в определённом порядке, то должна быть предусмотрена и возможность проверить их на равенство.