Модуль, а также подпрограмма, это способ организации вашего кода. Когда мы разрабатываем программы, мы упаковываем инструкции в подпрограммы, подпрограммы в структуры, структуры в пакеты, библиотеки, сборки, инфраструктуры, решения и так далее. Итак, оставляя все остальное в стороне, это всего лишь механизм для организации вашего кода.
Основная причина, по которой мы используем все эти механизмы, вместо того, чтобы просто выкладывать наши инструкции линейно, заключается в том, что сложность программы возрастает нелинейно по отношению к ее размеру. Другими словами, программу, составленную из n
частей, каждая из которых имеет m
инструкции, легче понять, чем программу, которая построена из n*m
инструкций. Это, конечно, не всегда так (иначе мы можем просто разбить нашу программу на произвольные части и быть счастливыми). Фактически, чтобы это было правдой, мы должны ввести один существенный механизм, называемый абстракция . Мы можем извлечь выгоду из разделения программы на управляемые части, только если каждая часть обеспечивает своего рода абстракцию. Например, мы можем иметь, connect_to_database
, query_for_students
, sort_by_grade
и take_the_first_n
абстракции, упакованные как функции или подпрограммы, и гораздо проще понять код, который выражается в терминах этих абстракций, чем пытаться чтобы понять код, в котором встроены все эти функции.
Итак, теперь у нас есть функции, и естественно ввести следующий уровень организации - наборы функций. Обычно видно, что некоторые функции создают семейства вокруг некоторой общей абстракции, например, student_name
, student_grade
, student_courses
и т. Д., Все они вращаются вокруг одной и той же абстракции student
. То же самое относится к connection_establish
, connection_close
и т. Д. Поэтому нам нужен механизм, который свяжет эти функции. Здесь мы начинаем иметь варианты. Некоторые языки выбрали путь ООП, в котором объекты и классы являются единицами организации. Где набор функций и состояния называется объектом. Другие языки пошли другим путем и решили объединить функции в статические структуры, называемые modules . Основное отличие состоит в том, что модуль представляет собой статическую структуру времени компиляции, где объекты - это структуры времени выполнения, которые должны быть созданы во время выполнения для использования. В результате, естественно, объекты имеют тенденцию содержать состояние, а модули - нет (и содержат только код). А объекты по своей природе являются обычными значениями, которые вы можете назначать переменным, хранить их в файлах и выполнять другие манипуляции, которые вы можете делать с данными. Классические модули, в отличие от объектов, не имеют представления времени выполнения, поэтому вы не можете передавать модули в качестве параметров своим функциям, сохранять их в списке и иным образом выполнять какие-либо вычисления для модулей. Это в основном то, что люди имеют в виду, говоря гражданин первого класса - способность рассматривать сущность как простую ценность.
Вернуться к составным программам. Чтобы сделать объекты / модули компонуемыми, мы должны быть уверены, что они создают абстракции. Для функций граница абстракции четко определена - это кортеж параметров. Для объектов у нас есть понятие интерфейсов и классов. Пока для модулей у нас есть только интерфейсы. Поскольку модули по своей природе более просты (они не включают в себя состояние), нам не нужно иметь дело с их конструированием и деконструкцией, поэтому нам не нужно более сложное понятие класса . И классы, и интерфейсы - это способ классификации объектов и модулей по некоторым критериям, так что мы можем рассуждать о разных модулях, не обращая внимания на реализацию, так же, как мы делали с connect_to_database
, query_for_students
и другими функциями - мы были рассуждения о них только на основании их имени и интерфейса (и, вероятно, документации). Теперь у нас может быть класс student
или модуль Student
, которые определяют абстракцию, называемую студентом, так что мы можем сэкономить много умственных способностей, не прибегая к тому, как эти студенты реализуются.
И, помимо упрощения понимания наших программ, абстракции дают нам еще одно преимущество - обобщение . Поскольку нам не нужно рассуждать о реализации функции или модуля, это означает, что все реализации в некоторой степени взаимозаменяемы. Поэтому мы можем написать наши программы так, чтобы они выражали свое поведение в общем виде, не нарушая абстракций, а затем выбирали конкретные экземпляры при запуске наших программ. Объекты являются экземплярами времени выполнения, и по сути это означает, что мы можем выбрать нашу реализацию во время выполнения. Что приятно. Классы, однако, редко бывают первоклассными гражданами, поэтому нам приходится изобретать разные громоздкие методы для выбора, такие как шаблоны проектирования Abstract Factory и Builder. Для модулей ситуация еще хуже, так как они по своей природе являются структурой времени компиляции, мы должны выбрать нашу реализацию во время сборки / размещения программы. Что не то, что люди хотят делать в современном мире.
А вот и первоклассные модули, являющиеся объединением модулей и объектов, они дают нам лучшее из двух миров - легко рассуждать о структурах без гражданства, которые в то же время являются чистыми первоклассными гражданами. , который можно сохранить в переменной, поместить в список и выбрать нужную реализацию во время выполнения.
Говоря об OCaml, под капотом первоклассные модули - просто запись функций. В OCaml вы даже можете добавить состояние к первоклассному модулю, делая его практически неотличимым от объекта. Это подводит нас к другой теме - в реальном мире разделение между объектами и структурами не так очевидно. Например, OCaml предоставляет как модули, так и объекты, и вы можете помещать объекты в модули и даже наоборот. В C / C ++ у нас есть модули компиляции, видимость символов, непрозрачные типы данных и файлы заголовков, которые позволяют выполнять какое-то модульное программирование, а также структуры и пространства имен. Поэтому разницу иногда трудно понять.
Поэтому подведем итоги. Модули представляют собой фрагменты кода с четко определенным интерфейсом для доступа к этому коду. Модули первого класса - это модули, которыми можно манипулировать как обычным значением, например хранить в структуре данных, назначать переменную и выбирать во время выполнения.