Prelude> (1::Int) + (1::Double)
< interactive>:2:13:
Couldn’t match expected type ‘Int’ with actual type ‘Double’
In the second argument of ‘(+)’, namely ‘(1 :: Double)’
In the expression: (1 :: Int) + (1 :: Double)
In an equation for ‘it’: it = (1 :: Int) + (1 :: Double)
Любое преобразование типов контролируется пользователем. Мы должны вызвать специальную функ-
цию.
От целых к действительным: Часто возникает необходимость приведения целых чисел к действитель-
ным при делении. Для этого можно воспользоваться функцией: fromIntegral
Prelude> :i fromIntegral
fromIntegral :: (Integral a, Num b) => a -> b
-- Defined in ‘GHC.Real’
Определим функцию поиска среднего между двумя целыми числами:
meanInt :: Int -> Int -> Double
meanInt a b = fromIntegral (a + b) / 2
Арифметика | 35
В этой функции двойка имеет тип Double. Обратите внимание на скобки: составной синоним всегда при-
тягивает аргументы сильнее чем бинарная операция.
От действительных к целым: В этом нам поможет класс RealFrac. Методы говорят сами за себя:
Prelude GHC.Float> :i RealFrac
class (Real a, Fractional a) => RealFrac a where
properFraction :: Integral b => a -> (b, a)
truncate :: Integral b => a -> b
round :: Integral b => a -> b
ceiling :: Integral b => a -> b
floor :: Integral b => a -> b
-- Defined in ‘GHC.Real’
instance RealFrac Float -- Defined in ‘GHC.Float’
instance RealFrac Double -- Defined in ‘GHC.Float’
Метод properFraction отделяет целую часть числа от дробной:
properFraction :: Integral b => a -> (b, a)
Для того, чтобы вернуть сразу два значения используется кортеж (кортежи пишутся в обычных скобках,
значения следуют через запятую):
Prelude> properFraction 2.5
(2,0.5)
Для пар (кортеж, состоящий из двух элементов) определены две удобные функции извлечения элементов,
их смысл можно понять по одним лишь типам:
fst :: (a, b) -> a
snd :: (a, b) -> b
Проверим:
Prelude> let x = properFraction 2.5
Prelude> (fst x, snd x)
(2, 0.5)
Мы бы и сами могли определить такие функции:
fst :: (a, b) -> a
fst (a, _) = a
snd :: (a, b) -> b
snd (_, b) = b
Между действительными числами: Кто-то написал очень хорошую функцию, но она определена на
Double, а вам приходится использовать Float. Как быть? Нам поможет функция realToFrac:
Prelude> :i realToFrac
realToFrac :: (Real a, Fractional b) => a -> b
-- Defined in ‘GHC.Real’
Она принимает значение из класса Real и приводит его к значению, которое можно делить. Что это за
класс Real? Математики наверное смекнут, что это противоположность комплексным числам (где-то должен
быть определён тип или класс Complex, и он правда есть, но об этом в следующем разделе). При переходе
к комплексным числам мы теряем способность сравнения на больше/меньше, но сохраняем возможность
вычисления арифметических операций, поэтому класс Real это пересечение классов Num и Ord:
Prelude> :i Real
class (Num a, Ord a) => Real a where
toRational :: a -> Rational
Здесь “пересечение” означает “и тот и другой”. Пересечение классов кодируется с помощью контекста.
Вернёмся к нашему первому примеру:
36 | Глава 2: Первая программа
Prelude> realToFrac (1::Float) + (1::Double)
2.0
Отметим, что этой функцией можно пользоваться не только для типов Float и Double, в Haskell возможны
самые экзотические числа.
Если преобразования между Float и Double происходят очень-очень часто, возможно имеет смысл вос-
пользоваться специальными для GHC функциями: Они определены в модуле GHC.Float:
Prelude> :m +GHC.Float
Prelude GHC.Float> :t float2Double
float2Double :: Float -> Double
Prelude GHC.Float> :t double2float
double2Float :: Double -> Float
2.7 Документация
К этой главе мы уже рассмотрели основные конструкции языка и базовые типы. Если у вас есть какая-то