KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Миран Липовача - Изучай Haskell во имя добра!

Миран Липовача - Изучай Haskell во имя добра!

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Миран Липовача, "Изучай Haskell во имя добра!" бесплатно, без регистрации.
Перейти на страницу:

["827–91-62","943–29-29","493–29-28"]

Превосходно!

Ещё примеры. Допустим, мы делаем отображение из ассоциативного списка чисел и при обнаружении повторяющегося ключа хотим, чтобы сохранилось наибольшее значение. Это можно сделать так:

ghci> Map.fromListWith max [(2,3),(2,100),(3,29),(3,11),(4,22),(4,15)]

fromList [(2,100),(3,29),(4,22)]

Или хотим, чтобы значения с повторяющимися ключами складывались:

ghci> Map.fromListWith (+) [(2,3),(2,100),(3,29),(3,11),(4,22),(4,15)]

fromList [(2,103),(3,40),(4,37)]

Ну что ж, модуль Data.Map, да и другие модули из стандартной библиотеки языка Haskell довольно неплохи. Далее посмотрим, как написать свой собственный модуль.

Написание собственных модулей

Практически все языки программирования позволяют разделять код на несколько файлов, и 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 и т. д., и каждая функция вычислит площадь или объём соответствующего объекта.

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

Позднее при написании программы со схожей функциональностью вы сможете просто импортировать свой модуль.

7

Создание новых типов и классов типов

В предыдущих главах мы изучили некоторые типы и классы типов в языке 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 объявлен иначе – видите, я пропустил уйму чисел – такая запись полезна лишь в иллюстративных целях.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*