Основы функционального программирования/Haskell/Модули и монады: различия между версиями

Содержимое удалено Содержимое добавлено
м Шаблон со списком лекций
неполное форматирование.
Строка 1:
{{ОФП Содержание}}
 
= Лекция 6. «Модули и монады в Haskell’еХаскеле» =
 
Ни один язык программирования не обходится без понятия [[w:модуль (программирование)|модуля]], разве что только языки самого [[w:Низкоуровневый язык программирования|низкого уровня]], да и те в последнее время приобретают свойства языков более высокого уровня. Модули пришли из давнего прошлого, когда программирование, как искусство (или ремесло), только развивалось. Возникла необходимостьнадобность разбивать программы на логические части, каждую из которых создавал бы отдельный разработчик или коллектив разработчиков.
 
В Haskell’е[[w:Haskell|Хаскеле]] также существуетесть понятие модуля. ОднакоНо более интригующим и завораживающимзавораживающее неофитов явлениемявление в языке являетсяесть понятие «монада»'''монады'''. Далее вВ этой лекции будут подробно рассмотрены оба понятия — «модуль» и «монада».
 
== Модули ==
 
В Haskell’еХаскеле модули несут двоякое назначение: с одной стороны, модулиони необходимы для контроля за пространством имён (как, собственно, и во всех других языках программирования), с другой стороны, при помощи модулей можно создавать абстрактные типы данных.
 
ОпределениеОпределить модулямодуль в Haskell’еХаскеле достаточно просто. Именем модуля может быть любой символ, начинается имя только с заглавнойпрописной буквы. Дополнительно имя модуля никак не связано с файловой системой (как, например, в Pascal’еПаскале и в Java), т.ето есть имя файла, содержащего модуль, может быть не таким же, как и название модуля. На самом деле, в одном файле может быть несколько модулей, т.к.так как модуль — это всего лишь навсего декларация самого высокого уровня.
 
Как известно, на верхнем уровне модуля в Haskell’еХаскеле может быть множество деклараций (описаний и определений): типы, классы, данные, функции. Однако один вид деклараций должен стоять в модуле на первом месте (если этот вид деклараций вообще используется). Речь идет о включении в модуль других модулей; для этого используется служебное слово import. Остальные определения могут появляться в любой последовательности.
 
Определение модуля должно начинаться со служебного слова module. Например, ниже приведено определение модуля Tree:
Строка 26:
fringe (Branch left right) = fringe left ++ fringe right
 
В этом модуле описан один тип (Tree — ничего страшного, что имя типа совпадает с названием модуля, в данном случае они находятся в различных пространствах имён) и одна функция (fringe). В данном случаеЗдесь модуль Tree явно экспортирует тип Tree (вместе со своими подтипами Leaf и Branch) и функцию fringe — для этого имена типа и функции указаны в скобках после имени модуля. Если наименование какого-либо объекта не указывать в скобках, то он не будет экспортироваться, т.е.то есть этот объект не будет виден извне текущего модуля.
 
Использование модуля в другом модуле выглядит еще проще:
Строка 36:
main = print (fringe (Branch (Leaf 1) (Leaf 2)))
 
В приведенном примере видно, что модуль Main импортирует модуль Tree, причём в декларации import явно описано, какие именно объекты импортируются из модуля Tree. Если это описание опустить, то импортироваться будут все объекты, которые модуль экспортирует, т.е.то есть в данном случае можно было просто написать: import Tree.
 
Бывает так, что один модуль импортирует несколько других (надо заметить, что это обычная ситуация), но при этом в импортируемых модулях существуют объекты с одним и тем же именем. Естественно, что в этом случае возникает конфликт имён. Чтобы этого избежать в Haskell’еХаскеле существует специальное служебное слово qualified, при помощи которого определяются те импортируемые модули, имена объектов в которых приобретают вид: <Имя Модуля>.<Имя Объекта>, т.е.то есть для того, чтобы обратиться к объекту из квалифицированного модуля, перед его именем необходимо написать имя модуля:
 
module Main where
Строка 50:
=== Абстрактные типы данных ===
 
В Haskell’еХаскеле модуль является единственным способом создать так называемые абстрактные типы данных, т.е.то есть такие, в которых скрыто представление типа, но открыты только специфические операции над созданным типом, набор которых вполне достаточен для работы с типом. Например, хотя тип Tree является достаточно простым, его все-таки лучше сделать абстрактным типом, т.е.то есть скрыть то, что Tree состоит из Leaf и Branch. Это делается следующим образом:
 
module TreeADT (Tree, leaf, branch, cell, left, right, isLeaf) where
Строка 65:
isLeaf _ = False
 
Видно, что к внутренностям типа Tree внешний пользователь (программист) может пробраться только прииспользуя помощиопределённые использования определённых функцийфункции. Впоследствии, когда создатель этого модуля захочет изменить представление типа (например, оптимизировать его), ему необходимо будет изменить и функции, которые оперируют полями типа Tree. В свою очередь, программист, который использовалиспользовавший в своей программе тип Tree, ничего менять не будет, т.к.ибо его программа все так же останется работоспособной.
 
=== Другие аспекты использования модулей ===
 
Далее приводятся дополнительные аспекты системы модулей в Haskell’еХаскеле:
 
*В декларации импорта (import) можно выборочно спрятать некоторые из экспортируемых объектов (прислужебным помощи служебного словасловом hiding). Это бывает полезным для явного исключения определений некоторых объектов из импортируемого модуля.
*При импорте можно определить псевдоним модуля для квалификации имен экспортируемых из него объектов. Для этого используется служебное слово as. Это может быть полезным для того укорачивания имен модулей.
*Все программы неявно импортируют модуль Prelude. Если сделать явный импорт этого модуля, то в его декларации возможно скрыть некоторые объекты, чтобы впоследствии их переопределить.
Строка 79:
== Монады ==
 
Многие новички в функциональном програмировании бывают часто озадачены понятием монады в Haskell’еХаскеле. Но монады очень часто встречаются в языке,: так, например, система ввода/вывода основана именно на понятии монады, а стандартные библиотеки содержат целые модули, посвященныепосвящённые монадам. Необходимо отметить, что понятие монады в Haskell’еХаскеле основано на [[w:теория категорий|теории категорий]], однако для того,но чтобы не вдаваться в абстрактную математику, далее будет представлено интуитивное понимание монад.
 
Монады являютсясуть типамитипы, которые представляютпредставляющие собой экземпляры одного из следующих монадических классов: Functor, Monad и MonadPlus. Ни один из этих классов не может являтьсябыть предком для другого класса, т.е.то есть монадические классы ненаследуемы. В модуле Prelude определены три монады: IO, [] и Maybe, т.е.то есть список также является монадой.
 
Математически монада определяется через набор правил, которые связывают операции, производимые над монадой. Эти правила дают интуитивное понимание того, как должна использоваться монада и какова ее внутренняя структура. Для конкретизации далее рассматривается класс Monad, в котором определены две базовые операции и одна функция:
Строка 96:
Точное значение операция связывания конечно же зависит от конкретной реализации монады. Так, например, тип IO определяет операцию (>>=) как последовательное выполнение двух её операндов, а результат выполнения первого операнда последовательно передается во второй. Для двух других встроенных монадических типов (списки и Maybe) эта операция определена как передача нуля или более параметров из одного вычислительного процесса в следующий.
 
В Haskell’еХаскеле определено специальное служебное слово, которое на уровне языка поддерживаетподдерживающее использование монад. Это: слово do, понимание которого можно увидеть в следующих правилах его применения:
 
do e1 ; e2 = e1 >> e2