Базовые типы языка программирования Haskell

Haskell - типизированный язык со строгой статической системой типов. Термин "строгий" означает, что отсутствует неявное приведение типов. Термин "статический" означает, что проверка типов происходит не во время исполнения, а во время компиляции. Haskell умеет выводить тип практически любого выражения самостоятельно. Если выражение правильно построено, то тогда его тип выводится, а если в выражении содержится какая-то ошибка, то тогда система вывода типов Haskell возвращает сообщение об ошибке. 

Имена типов начинаются с большой буквы. Два двоеточия подряд - это оператор типизации в Haskell. Этот оператор связывает выражения, которые стоят слева от этого оператора с типами, которые стоят справа.

В Haskell имеется стандартный набор числовых типов:
  • Int - для целых ограниченного размера;
  • Integer - для целых произвольного размера;
  • Float - числа с плавающей точкой одинарной точности;
  • Double - числа с плавающей точкой двойной точности.
Все эти типы являются представителями одного класса типов - Num. Механизм классов типов используется для того чтобы задать для всех чисел общий интерфейс. Например, чтобы числа относящиеся к разным типам например, Double и Int можно было складывать с помощью одного и того же оператора сложения. 

Char

Тип Char является простейшим типом. Его населяют все символы алфавита, юникода и управляющие последовательности.
Prelude> :type 'c'
'c' :: Char
Prelude> :type '\n'
'\n' :: Char

Bool

Prelude> :type True
True :: Bool
Prelude> :t False
False :: Bool

Num

Числовые константы без точки принадлежат классу типов Num.
Prelude> :t 3
3 :: Num a => a
Тип числового литерала 3 это некоторое произвольное значение a, которое присутствует с некоторым выражением, которое называется контекстом. На a накладывается некоторое ограничение: a должно быть представителем класса типов Num.

Определение класса типов Num:
class Num a where
 (+), (-), (*) :: a -> a -> a
 negate :: a -> a -- унарный префиксный минус
 abs :: a -> a -- модуль числа
 signum :: a -> a -- знак числа
 fromInteger :: Integer -> a -- переход от значений типа Integer к любому числовому типу
 -- реализация по умолчанию:
 x - y = x + negate y
 negate x = 0 - x

{-
LAW abs x * signum x == x
-}

На самом деле все численные константы, которые определены в языке, в реализации GHCi определены как представители типа Integer. И всегда когда мы их используем, они при трансляции заменяются на вызов функции fromInteger на некотором числе.
Prelude> fromInteger 3
3
Prelude> :t fromInteger 3
fromInteger 3 :: Num a => a
Функция fromInteger позволяет переходить от значения типа Integer к произвольному числовому типу. Естественно, что это поведение это всего лишь детали реализации GHCi. В стандарте языка просто сказано, что все числа являются полиморфными.

В Haskell иногда для классов типов формулируются законы. Компилятор эти законы не может проверять, но для того чтобы класс типов разумным образом можно было использовать выполнение этих законов требуется от программиста. Закон для класса типов Num такой, модуль числа умноженный на знак числа должен равняться самому числу.

Деление в классе типов Num не определено потому что деление для целых чисел и чисел с плавающей точкой реализовано совершенно по-разному.

У класса типов Num есть два расширения (наследника): класс типов Integral и Fractional. Класс типов Integral отвечает за целочисленное деление. Класс типов Fractional отвечает за деление типов с плавающей точкой.

Integral 

Prelude> :i Integral
class (Real a, Enum a) => Integral a where
  quot :: a -> a -> a
  rem :: a -> a -> a
  div :: a -> a -> a
  mod :: a -> a -> a

  quotRem :: a -> a -> (a, a)
  divMod :: a -> a -> (a, a)
  toInteger :: a -> Integer
   -- Defined in ‘GHC.Real’
instance Integral Word -- Defined in ‘GHC.Real’
instance Integral Integer -- Defined in ‘GHC.Real’
instance Integral Int -- Defined in ‘GHC.Real’

Real наследует Ord и Num. Основные функции целочисленного деления это div и mod. Есть дополнительная функция, которая называется divMod - она возвращает пару, т.е. она выполняет работу и div и mod одновременно. Функции quot и rem тоже выполняют целочисленное деление. Дело в том что целочисленное деление на отрицательных числах может быть определено двумя разными способами. Не вдаваясь в подробности, там возникает проблема со знаками. Функции div и mod это стандартное целочисленное деление. Функции quot и rem работают слегка эффективнее, но результат по знаку немножко отличается от div и mod. Функция toInteger приводит любой интегральный тип к типу Integer. Стандартные представители класса типов Integral это Integer и Int.


Следующая функция с сигнатурой avg :: Int -> Int -> Int -> Double вычисляет среднее значение переданных в нее аргументов.
avg a b c = (fromIntegral $ a + b + c) / 3.0

GHCi> avg 3 4 8
5.0


Fractional

Числовые константы с точкой принадлежат классу типов Fractional. К классу типов Fractional относятся типы Double и Float.
Prelude> :t 3.5
3.5 :: Fractional a => a

Fractional - это другая ветвь наследования от класса типов Num. Fractional определяет полноценное деление. К классу типов Fractional относятся Float и Double. Есть еще тип рациональных чисел в котором тоже полноценное деление определено. Тип рациональных чисел состоит из числителя и знаменателя. Функция fromRational преобразует тип рациональных чисел к типу Fractional.
Prelude> :i Fractional
class Num a => Fractional a where
  (/) :: a -> a -> a
  recip :: a -> a
  fromRational :: Rational -> a
   -- Defined in ‘GHC.Real’
instance Fractional Float -- Defined in ‘GHC.Float’
instance Fractional Double -- Defined in ‘GHC.Float’

Floating

От класса типов Fractional наследуется класс типов Floating. В этом классе типов определена все стандартные математические функции.
Prelude> :i Floating
class Fractional a => Floating a where
  pi :: a
  exp :: a -> a
  log :: a -> a
  sqrt :: a -> a
  (**) :: a -> a -> a
  logBase :: a -> a -> a
  sin :: a -> a
  cos :: a -> a
  tan :: a -> a
  asin :: a -> a
  acos :: a -> a
  atan :: a -> a
  sinh :: a -> a
  cosh :: a -> a
  tanh :: a -> a
  asinh :: a -> a
  acosh :: a -> a
  atanh :: a -> a
   -- Defined in ‘GHC.Float’
instance Floating Float -- Defined in ‘GHC.Float’
instance Floating Double -- Defined in ‘GHC.Float’
Представителями этого класса типов служит Float и Double, т.е. числа с плавающей точкой.

RealFrac

RealFrac наследуется от Real и от Floating. Этот класс типов полезен тем, что он содержит целый набор функций связанных с округлением значений с плавающей точкой до целых типов. Функция truncate просто отбрасывает десятичную часть. Функция round, которая округляет, т.е. выбирает наиболее близкое значение интегрального типа. Функции ceiling и floor задают соответственно верхнюю и нижнюю целочисленную грань числа с плавающей точкой.
Prelude> :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’
Float и Double являются представителями этого класса типов.

RealFloat

RealFloat - это довольно технический класс типов, который описывает внутреннее представление чисел с плавающей точкой. На самом деле числа с плавающей точкой внутри компьютера реализованы в виде мантиссы, экспоненты, основания... вот вся соответствующая информация о данном конкретном типе с плавающей точкой присутствует в классе RealFloat.
Prelude> :i RealFloat
class (RealFrac a, Floating a) => RealFloat a where
  floatRadix :: a -> Integer
  floatDigits :: a -> Int
  floatRange :: a -> (Int, Int)
  decodeFloat :: a -> (Integer, Int)
  encodeFloat :: Integer -> Int -> a
  exponent :: a -> Int
  significand :: a -> a
  scaleFloat :: Int -> a -> a
  isNaN :: a -> Bool
  isInfinite :: a -> Bool
  isDenormalized :: a -> Bool
  isNegativeZero :: a -> Bool
  isIEEE :: a -> Bool
  atan2 :: a -> a -> a
   -- Defined in ‘GHC.Float’
instance RealFloat Float -- Defined in ‘GHC.Float’
instance RealFloat Double -- Defined in ‘GHC.Float’


Int

Можно явно задать конкретный тип:
Prelude> let x = 3 :: Int
Prelude> x
3
Prelude> :type x
x :: Int

Integer

Тип Integer может содержать константы произвольной длины.
Prelude> 123456789123456789123456789123456789123456789123456789 :: Integer
123456789123456789123456789123456789123456789123456789

Double

Prelude> let y = 3 :: Double
Prelude> y
3.0
Prelude> :t y
y :: Double
Prelude> let z = y + 17
Prelude> z
20.0
Prelude> :t z
z :: Double
Переменная z имеет тип Double, хотя относительно константы 17 тип неизвестен, она полиморфная - может относится к любому представителю класса типов Num. Поскольку константа y имеет тип Double и сложение реализовано таким образом, что оба аргумента и возвращаемое значение должны относиться к одному и тому же типу, получается, что z имеет тип Double.


В реальных финансовых приложениях использовать тип с плавающей точкой для хранения подобной информации не рекомендуется.

Комментариев нет:

Отправить комментарий