Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Написание собственных модулей
Практически все языки программирования позволяют разделять код на несколько файлов, и Haskell – не исключение. При написа- нии программ очень удобно помещать функции и типы, служащие схожим целям, в отдельный модуль. Таким образом, можно будет повторно использовать эти функции в других программах, просто импортировав нужный модуль.
Мы говорим, что модуль эк- спортирует функции. Это зна- чит, что когда мы его импорти- руем, то можем использовать экспортируемые им функции. Модуль может определить функции для внутреннего ис- пользования, но извне модуля мы видим только те, которые он экспортирует.
Модуль Geometry Давайте разберём процесс создания модулей на простом приме- ре. Создадим модуль, который содержит функции для вычисления объёма и площади поверхности нескольких геометрических фигур. И начнём с создания файла Geometry.hs. В начале модуля указывается его имя. Если мы назвали файл Geometry.hs, то имя нашего модуля должно быть Geometry. Затем сле- дует перечислить экспортируемые функции, после чего мы можем писать сами функции: module Geometry (sphereVolume , sphereArea , cubeVolume , cubeArea , cuboidArea , cuboidVolume ) where Как видите, мы будем вычислять площади и объёмы для сфер (sphere), кубов (cube) и прямоугольных параллелепипедов (cuboid). Сфера – это круглая штука наподобие грейпфрута, куб – квадратная штука, похожая на кубик Рубика, а прямоугольный параллелепи- пед – точь-в-точь пачка сигарет. (Дети, курить вредно!) Продолжим и определим наши функции: module Geometry (sphereVolume , sphereArea , cubeVolume
, cubeArea , cuboidArea , cuboidVolume ) where
sphereVolume:: Float –> Float sphereVolume radius = (4.0 / 3.0) * pi * (radius
ˆ 3)
sphereArea:: Float –> Float sphereArea radius = 4 * pi * (radius
ˆ 2)
cubeVolume:: Float –> Float cubeVolume side = cuboidVolume side side side
cubeArea:: Float –> Float cubeArea side = cuboidArea side side side
cuboidVolume:: Float –> Float –> Float –> Float cuboidVolume a b c = rectArea a b * c
cuboidArea:: Float –> Float –> Float –> Float cuboidArea a b c = rectArea a b * 2 + rectArea a c * 2 + rectArea c b * 2
rectArea:: Float –> Float –> Float rectArea a b = a * b Довольно стандартная геометрия, но есть несколько вещей, на которые стоит обратить внимание. Так как куб – это разновидность параллелепипеда, мы определили его площадь и объём, трактуя куб как параллелепипед с равными сторонами. Также мы определили вспомогательную функцию rectArea, которая вычисляет площадь прямоугольника по его сторонам. Функция очень проста – она просто перемножает стороны. Заметьте, мы используем функ- цию rectArea в функциях модуля (а именно в функциях cuboidArea и cuboidVolume), но не экспортируем её, так как хотим создать мо- дуль для работы только с трёхмерными объектами.
При создании модуля мы обычно экспортируем только те функ- ции, которые служат интерфейсом нашего модуля, и скрываем ре- ализацию. Использующий наш модуль человек ничего не должен знать о тех функциях, которые мы не экспортируем. Мы можем полностью их поменять или удалить в следующей версии (скажем,
удалить определение функции rectArea и просто использовать ум- ножение), и никто не будет против – в первую очередь потому, что эти функции не экспортируются. Чтобы использовать наш модуль, запишем: import Geometry Файл Geometry.hs должен находиться в той же папке, что и им- портирующая его программа.
Иерархия модулей Модулям можно придать иерархическую структуру. Каждый модуль может иметь несколько подмодулей, которые в свою очередь также могут содержать подмодули. Давайте разделим наш модуль Geometry таким образом, чтобы в него входили три подмодуля, по одному на каждый тип объекта. Сначала создадим папку с именем Geometry. В этой папке мы разместим три файла: Sphere.hs, Cuboid.hs и Cube.hs. Посмотрим, что должно находиться в каждом файле. Вот содержимое файла Sphere.hs: module Geometry.Sphere (volume , area ) where
volume:: Float –> Float volume radius = (4.0 / 3.0) * pi * (radius
ˆ 3)
area:: Float –> Float area radius = 4 * pi * (radius
ˆ 2)
Файл Cuboid.hs выглядит так: module Geometry.Cuboid (volume , area ) where
volume:: Float –> Float –> Float –> Float volume a b c = rectArea a b * c
area:: Float –> Float –> Float –> Float area a b c = rectArea a b * 2 + rectArea a c * 2 + rectArea c b * 2
rectArea:: Float –> Float –> Float rectArea a b = a * b А вот и содержимое файла Cube.hs: module Geometry.Cube (volume , area ) where import qualified Geometry.Cuboid as Cuboid volume:: Float –> Float volume side = Cuboid.volume side side side
area:: Float –> Float area side = Cuboid.area side side side Обратите внимание, что мы поместили файл Sphere.hs в папку с именем Geometry и определили имя модуля как Geometry.Sphere. То же самое мы сделали для куба и параллелепипеда. Также отметьте, что во всех трёх модулях определены функции с одинаковыми име- нами. Мы вправе так поступать, потому
что функции находятся в разных моду- лях. Итак, если мы редактируем файл, ко- торый находится на одном уровне с пап- кой Geometry, то запишем: import Geometry.Sphere после чего сможем вызывать функции area и volume, которые вычислят пло- щадь и объём сферы. Если нам потре- буется использовать несколько наших модулей, мы должны выполнить квали- фицированный импорт, потому что они экспортируют функции с одинаковыми
именами. Делаем так: import qualified Geometry.Sphere as Sphere import qualified Geometry.Cuboid as Cuboid import qualified Geometry.Cube as Cube Затем мы сможем вызывать функции Sphere.area, Sphere.volume, Cuboid.area и т. д., и каждая функция вычислит площадь или объём соответствующего объекта. В следующий раз, когда вы поймаете себя за написанием огром- ного файла с кучей функций, попытайтесь выяснить, какие функ- ции служат некоей общей цели, и можно ли включить их в отде- льный модуль. Позднее при написании программы сосхожей функциональнос- тью вы сможете просто импортировать свой модуль.
СОЗДАНИЕ НОВЫХ ТИПОВ И КЛАССОВ ТИПОВ В предыдущих главах мы изучили некоторые типы и классы типов в языке Haskell. Из этой главы вы узнаете, как создать и заставить работать свои собственные!
Введение в алгебраические типы данных До сих пор мы сталкивались со мно- гими типами данных – Bool, Int, Char, Maybe и др. Но как создать свой собс- твенный тип? Один из способов – ис- пользовать ключевое слово data. Да- вайте посмотрим, как в стандартной библиотеке определён тип Bool: data Bool = False | True Ключевое слово data объявляет новый тип данных. Часть до знака равенства вводит идентифика- тор типа, в данном случае Bool. Часть после знака равенства – это конструкторы данных, которые также называют конструкторами значений. Они определяют, какие значения может принимать тип. Символ | означает «или». Объявление можно прочесть так: тип
Bool может принимать значения True или False. И имя типа, и конс- трукторы данных должны начинаться с прописной буквы. Рассуждая подобным образом, мы можем думать, что тип Int объявлен так: data Int = –2147483648 | –2147483647 |... | –1 | 0 | 1 | 2 |... | 2147483647 Первое и последнее значения – минимальное и максимальное для Int. На самом деле тип Int объявлен иначе – видите, я пропус- тил уйму чисел – такая запись полезна лишь в иллюстративных це- лях.
Отличная фигура за 15 минут Теперь подумаем, как бы мы представили некую геометрическую фигуру в языке Haskell. Один из способов – использовать кортежи. Круг может быть представлен как (43.1, 55.0, 10.4), где первое и второе поле – координаты центра, а третье – радиус. Вроде бы подходит, но такой же кортеж может представлять вектор в трёх- мерном пространстве или что-нибудь ещё. Лучше было бы опреде- лить свой собственный тип для фигуры. Скажем, наша фигура мо- жет быть кругом или прямоугольником. data Shape = Circle Float Float Float | Rectangle Float Float Float Float Ну и что это? Размышляйте следующим образом. Конструк- тор для значения Circle содержит три поля типа Float. Когда мы записываем конструктор значения типа, опционально мы можем добавлять типы после имени конструктора; эти типы определя- ют, какие значения будет содержать тип с данным конструктором. В нашем случае первые два числа – это координаты центра, третье число – радиус. Конструктор для значения Rectangle имеет четыре поля, которые также являются числами с плавающей точкой. Пер- вые два числа – это координаты верхнего левого угла, вторые два числа – координаты нижнего правого угла.
Когда я говорю «поля», то подразумеваю «параметры». Конст- рукторы данных на самом деле являются функциями, только эти функции возвращают значения типа данных. Давайте посмотрим на сигнатуры для наших двух конструкторов: ghci>:t Circle Circle:: Float –> Float –> Float –> Shape
ghci>:t Rectangle Rectangle:: Float –> Float –> Float –> Float –> Shape Классно, конструкторы значений – такие же функции, как лю- бые другие! Кто бы мог подумать!.. Давайте напишем функцию, которая принимает фигуру и воз- вращает площадь её поверхности: area:: Shape –> Float area (Circle _ _ r) = pi * r ^ 2 area (Rectangle x1 y1 x2 y2) = (abs $ x2 – x1) * (abs $ y2 – y1) Первая примечательная вещь в объявлении – это декларация типа. Она говорит, что функция принимает фигуру и возвраща- ет значение типа Float. Мы не смогли бы записать функцию типа Circle –> Float, потому что идентификатор Circle не является ти- пом; типом является идентификатор Shape. По той же самой при- чине мы не смогли бы написать функцию с типом True –> Int. Вто- рая примечательная вещь – мы можем выполнять сопоставление с образцом по конструкторам. Мы уже записывали подобные со- поставления раньше (притом очень часто), когда сопоставляли со значениями [], False, 5, только эти значения не имели полей. Только что мы записали конструктор и связали его поля с имена- ми. Так как для вычисления площади нам нужен только радиус, мы не заботимся о двух первых полях, которые говорят нам, где располагается круг. ghci> area $ Circle 10 20 10 314.15927 ghci> area $ Rectangle 0 0 100 100 10000.0 Ура, работает! Но если попытаться напечатать Circle 10 20 5 в командной строке интерпретатора, то мы получим ошибку. Пока Haskell не знает, как отобразить наш тип данных в виде строки. Вспомним, что когда мы пытаемся напечатать значение в команд- ной строке, интерпретатор языка Haskell вызывает функцию show, для того чтобы получить строковое представление значения, и за- тем печатает результат в терминале. Чтобы определить для нашего типа Shape экземпляр класса Show, модифицируем его таким образом:
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show) Не будем пока концентрировать внимание на конструкции deriving (Show). Просто скажем, что если мы добавим её в конец объявления типа данных, Haskell автоматически определит экзем- пляр класса Show для этого типа. Теперь можно делать так: ghci> Circle 10 20 5 Circle 10.0 20.0 5.0 ghci> Rectangle 50 230 60 90 Rectangle 50.0 230.0 60.0 90.0 Конструкторы значений – это функции, а значит, мы можем их отображать, частично применять и т. д. Если нам нужен список концентрических кругов с различными радиусами, напишем следу- ющий код: ghci> map (Circle 10 20) [4,5,6,6] [Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0]
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2017-02-17; просмотров: 168; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.12.71.237 (0.047 с.) |