Обычные типы в языке Haskell 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Обычные типы в языке Haskell



А вот обзор некоторых часто используемых типов.

® Тип Int обозначает целое число. Число 7 может быть типа

Int, но 7.2 – нет. Тип Int ограничен: у него есть минимальное


ОБЫЧНЫЕ ТИПЫ В ЯЗЫКЕ HASKELL 49

и максимальное значения. Обычно на 32-битных машинах максимально возможное значение типа Int – это 2 147 483 647,

а минимально возможное – соответственно, –2 147 483 648.

ПРИМЕЧАНИЕ. Мы используем компилятор GHC, в котором мно- жество возможных значений типа Int определено размером ма- шинного слова на используемом компьютере. Так что если у вас 64-битный процессор, вполне вероятно, что наименьшим значе- нием типа Int будет –263, а наибольшим 263–1.

®
 
 

Тип Integer обозначает... э-э-э... тоже целое число. Основная разница в том, что он не имеет ограничения, поэтому может представлять большие числа. Я имею в виду – очень большие. Между тем тип Int более эффективен. В качестве примера сохраните следующую функцию в файл:

factorial:: Integer –> Integer factorial n = product [1..n]

Затем загрузите этот файл в GHCi с помощью команды:l

 
 

и проверьте её:

ghci> factorial 50

 
 

30414093201713378043612608166064768844377641568960512000000000000

® Тип Float – это действительное число с плавающей точкой одинарной точности. Добавьте в файл ещё одну функцию:

 
 

circumference:: Float –> Float circumference r = 2 * pi * r

Загрузите дополненный файл и запустите новую функцию:

ghci> circumference 4.0

 
 

25.132742

® Тип Double – это действительное число с плавающей точ- кой двойной точности. Двойная точность означает, что для представления чисел используется вдвое больше битов, по- этому дополнительная точность требует большего расхода памяти. Добавим в файл ещё одну функцию:

circumference':: Double –> Double


 

 
 

circumference' r = 2 * pi * r

Загрузите дополненный файл и запустите новую функцию

ghci> circumference' 4.0

 
 

25.132741228718345

® Тип Bool – булевский. Он может принимать только два значе- ния: True и False.

® Тип Char представляет символ Unicode. Его значения запи- сываются в одинарных кавычках. Список символов является строкой.

® Кортежи – это типы, но тип кортежа зависит от его длины и от типа его компонентов. Так что теоретически количест- во типов кортежей бесконечно – а стало быть, перечислить их все в этой книге нет возможности. Заметьте, что пустой кортеж () – это тоже тип, который может содержать единс- твенное значение: ().

 

Типовые переменные

Некоторые функции могут работать сданнымиразныхтипов. Например, функция head принимает список и возвращает его первый элемент. При этом неважно, что именно этот список содержит – числа, символы или вообще другие списки. Функция должна работать со списками, что бы они ни содержали.

 
 

Как вы думаете, каков тип функции head? Проверим, воспользо- вавшись командой:t.

ghci>:t head head:: [a] –> a

Гм-м! Что такое a? Тип ли это? Мы уже отмечали, что все типы пишутся с большой буквы, так что это точно не может быть типом. В действительности это типовая переменная. Иначе говоря, a мо- жет быть любым типом.


 

Подобные элементы напоминают «дженерики» в других язы- ках – но только в Haskell они гораздо более мощные, так как позво- ляют нам легко писать самые общие функции (конечно, если эти функции не используют какие-нибудь специальные свойства конк- ретных типов).

Функции, в объявлении которых встречаются переменные типа, называются полиморфными. Объявление типа функции head выше означает, что она принимает список любого типа и возвра- щает один элемент того же типа.

ПРИМЕЧАНИЕ. Несмотря на то что переменные типа могут иметь имена, состоящие более чем из одной буквы, мы обычно называ- ем их a, b, c, d...

 
 

Помните функцию fst? Она возвращает первый компонент в паре. Проверим её тип:

ghci>:t fst

 
 

fst:: (a, b) –> a

Можно заметить, что функция fst принимает в качестве пара- метра кортеж, который состоит из двух компонентов, и возвраща- ет значение того же типа, что и первый компонент пары. Поэтому мы можем применить функцию fst к паре, которая содержит зна- чения любых двух типов.

Заметьте, что хотя a и b – различные переменные типа, они вов- се не обязаны быть разного типа. Сигнатура функции fst лишь озна- чает, что тип первого компонента и тип возвращаемого значения одинаковы.

 

Классы типов

Класс типов – интерфейс, оп- ределяющий некоторое по- ведение. Если тип является экземпляром класса типов, то он поддерживает и реализует поведение, описанное классом типов. Более точно можно ска- зать, что класс типов опреде- ляет набор функций, и если мы


 

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

 
 

мощью оператора ==. Посмотрим на его сигнатуру:

ghci>:t (==)

 
 

(==):: (Eq a) => a –> a –> Bool

Заметьте: оператор равенства == – это функция. Функциями так- же являются операторы +, *, –, / и почти все остальные операторы. Если имя функции содержит только специальные символы, по умол- чанию подразумевается, что это инфиксная функция. Если мы захо- тим проверить её тип, передать её другой функции или вызвать как префиксную функцию, мы должны поместить её в круглые скобки. Интересно... мы видим здесь что-то новое, а именно символ =>.

Всё, что находится перед символом =>, называется ограничением класса. Мы можем прочитать предыдущее объявление типа следу- ющим образом: «функция сравнения на равенство принимает два значения одинакового типа и возвращает значение типа Bool. Тип этих двух значений должен быть экземпляром класса Eq» (это и есть ограничение класса).

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

ПРИМЕЧАНИЕ. Важно отметить, что классы типов в языке Haskell не являются тем же самым, что и классы в объектно-ориентиро- ванных языках программирования.

У функции elem тип (Eq a) => a –> [a] –> Bool, потому что она при- меняет оператор == к элементам списка, чтобы проверить, есть ли в этом списке значение, которое мы ищем.

Далее приводятся описания нескольких базовых классов типов.

 

Класс Eq

Класс Eq используется для типов, которые поддерживают про- верку равенства. Типы, являющиеся его экземплярами, должны реализовывать функции == и /=. Так что если у нас есть ограничение


 

 
 

класса Eq для переменной типа в функции, то она может исполь- зовать == или /= внутри своего определения. Все типы, которые мы упоминали выше, за исключением функций, входят в класс Eq, и, следовательно, могут быть проверены на равенство.

ghci> 5 == 5 True

ghci> 5 /= 5 False

ghci> 'a' == 'a'

True

ghci> "Хо Хо" == "Хо Хо"

True

ghci> 3.432 == 3.432

 
 

True

 

Класс Ord

 
 

Класс Ord предназначен для типов, которые поддерживают отно- шение порядка.

ghci>:t (>)

 
 

(>):: (Ord a) => a –> a –> Bool

Все типы, упоминавшиеся ранее, за исключением функций, имеют экземпляры класса Ord. Класс Ord содержит все стандартные функции сравнения, такие как >, <, >= и <=. Функция compare прини- мает два значения одного и того же типа, являющегося экземпля- ром класса Ord, и возвращает значение типа Ordering. Тип Ordering может принимать значения GT, LT или EQ, означая, соответственно,

 
 

«больше чем», «меньше чем» и «равно».

ghci> "Абракадабра" < "Зебра"

True

ghci> "Абракадабра" `compare` "Зебра" LT

ghci> 5 >= 2 True

 
 

ghci> 5 `compare` 3 GT


 

Класс Show

 
 

Значения, типы которых являются экземплярами класса типов Show, могут быть представлены как строки. Все рассматривавшие- ся до сих пор типы (кроме функций) являются экземплярами Show. Наиболее часто используемая функция в классе типов Show – это, собственно, функция show. Она берёт значение, для типа которо- го определён экземпляр класса Show, и представляет его в виде строки.

ghci> show 3

"3"

ghci> show 5.334

"5.334"

 
 

ghci> show True "True"

 

Класс Read

 
 

Класс Read – это нечто противоположное классу типов Show. Функ- ция read принимает строку и возвращает значение, тип которого является экземпляром класса Read.

ghci> read "True" || False True

ghci> read "8.2" + 3.8

12.0

ghci> read "5" – 2

ghci> read "[1,2,3,4]" ++ [3]

 
 

[1,2,3,4,3]

Отлично. Но что случится, если попробовать вызвать read "4"?

ghci> read "4"

<interactive>:1:0:

Ambiguous type variable `a' in the constraint:

 
 

`Read a' arising from a use of `read' at <interactive>:1:0–7 Probable fix: add a type signature that fixes these type variable(s)

Интерпретатор GHCi пытается нам сказать, что он не знает, что именно мы хотим получить в результате. Заметьте: во время предыдущих вызовов функции read мы что-то делали с результатом


 

функции. Таким образом, интерпретатор GHCi мог вычислить, ка- кой тип ответа из функции read мы хотим получить.

Когда мы использовали результат как булево выражение, GHCi

 
 

«понимал», что надо вернуть значение типа Bool. А в данном слу- чае он знает, что нам нужен некий тип, входящий в класс Read, но не знает, какой именно. Давайте посмотрим на сигнатуру функции read.

ghci>:t read

 
 

read:: (Read a) => String –> a

 

ПРИМЕЧАНИЕ. Идентификатор String – альтернативное наиме- нование типа [Char]. Идентификаторы String и [Char] могут быть использованы взаимозаменяемо, но далее будет использоваться только String, поскольку это удобнее и писать, и читать.

 
 

Видите? Функция возвращает тип, имеющий экземпляр класса Read, но если мы не воспользуемся им позже, то у компилятора не будет способа определить, какой именно это тип. Вот почему ис- пользуются явные аннотации типа. Аннотации типа – способ явно указать, какого типа должно быть выражение. Делается это с помо- щью добавления символов:: в конец выражения и указания типа. Смотрите:

ghci> read "5":: Int 5

ghci> read "5":: Float 5.0

ghci> (read "5":: Float) * 4 20.0

ghci> read "[1,2,3,4]":: [Int] [1,2,3,4]

 
 

ghci> read "(3, 'a')":: (Int, Char) (3, 'a')

Для большинства выражений компилятор может вывести тип самостоятельно. Но иногда он не знает, вернуть ли значение типа Int или Float для выражения вроде read "5". Чтобы узнать, какой у него тип, язык Haskell должен был бы фактически вычислить read "5".

Но так как Haskell – статически типизированный язык, он дол- жен знать все типы до того, как скомпилируется код (или, в случае


 

GHCi, вычислится). Так что мы должны сказать языку: «Эй, это выражение должно иметь вот такой тип, если ты сам случайно не понял!»

 
 

Обычно компилятору достаточно минимума информации, чтобы определить, значение какого именно типа должна вернуть функция read. Скажем, если результат функции read помещается в список, то Haskell использует тип списка, полученный благодаря наличию других элементов списка:

ghci> [read "True", False, True, False] [True, False, True, False]

Так как read "True" используется как элемент списка булевых значений, Haskell самостоятельно определяет, что тип read "True" должен быть Bool.

 

Класс Enum

 
 

Экземплярами класса Enum являются последовательно упорядо- ченные типы; их значения можно перенумеровать. Основное пре- имущество класса типов Enum в том, что мы можем использовать его типы в интервалах списков. Кроме того, у них есть предыдущие и последующие элементы, которые можно получить с помощью функций succ и pred. Типы, входящие в этот класс: (), Bool, Char, Ordering, Int, Integer, Float и Double.

ghci> ['a'..'e'] "abcde"

ghci> [LT.. GT]

[LT,EQ,GT]

ghci> [3.. 5]

[3,4,5]

 
 

ghci>succ 'B' 'C'

 

Класс Bounded

 
 

Экземпляры класса типов Bounded имеют верхнюю и нижнюю гра- ницу.

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. (Наверное, так сложилось


НЕСКОЛЬКО ЗАКЛЮЧИТЕЛЬНЫХ СЛОВ О КЛАССАХ ТИПОВ 59

 
 

исторически – хотя, по-моему, какова бы ни была причина, это довольно глупо.) В любом случае, если мы попробуем вычислить длину списка и добавить к ней 3.2, то получим ошибку, потому что мы попытались сложить значения типа Int и число с плавающей точкой. В этом случае можно использовать функцию fromIntegral:

ghci> fromIntegral (length [1,2,3,4]) + 3.2

 
 

7.2

 



Поделиться:


Последнее изменение этой страницы: 2017-02-17; просмотров: 266; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.141.202.54 (0.092 с.)