LINUX.ORG.RU

Идеальная система модулей в языке программирования

 , ,


0

3

Навеяно одной из соседних тем. Стало интересно, что вообще люди думают по этому поводу.

Какой бы вы хотели видеть систему модулей?

Тут можно выделить ряд вопросов.

Должны ли модули быть файлами(один файл - один модуль) или они должны декларироваться в коде? Или это можно как-то скомбинировать?

Должен ли модуль быть единицей инкапсуляции или же это лучше оставить системе типов(классы в случае ООП)?

Должен ли модуль формировать пространство имен? Как должен работать import?

Нужно ли явно отделять интерфейс модуля от реализации в коде(или даже их стоит по разным файлам разносить)?

Как система модулей сочетается с системой пакетов/зависимостей? Как сочетается с системой сборки?

И т.д. и т.п.

Делитесь мыслями и идеями=)


Ответ на: комментарий от anonymous

Да, достаточно интересная система. Но не слишком ли она сложна?

forCe ()

Должны ли модули быть файлами(один файл - один модуль) или они должны декларироваться в коде? Или это можно как-то скомбинировать?

Нужны вложенные модули. Выносить внутренние модули в отдельные файлы тоже бывает нужно, так что и многофайловые модули тоже нужны.

anonymous ()
Ответ на: комментарий от anonymous

Нужны вложенные модули

А может нужны «пакеты», представляющие собой набор модулей? И пакеты как раз могут быть вложенными?

так что и многофайловые модули тоже нужны.

Так может быть пакеты - директории, а модули - файлы в них?

forCe ()
Ответ на: комментарий от forCe

Да нет, не сильно. Наоборот, если вникнуть, то она кажется очень простой и в то же время мощной. Всё, что нужно есть (сигнатуры, инкапсуляция, типобезопасность, функторы), ничего лишнего нет (ущербных макросистем разрешения зависимостей)

Ну и кроме того, тема была «идеальная система модулей», а лучше пока ничего не придумали.

Кто мне приведёт пример функторов в других языках - буду благодарен. Только чтобы так же было просто писать/читать/использовать. Шаблоны С++ сразу отпадают, этими портянками только детей пугать.

anonymous ()
Ответ на: комментарий от anonymous

Наоборот, если вникнуть, то она кажется очень простой и в то же время мощной.

Я вроде бы вникал в свое время. Но на практике использовать не пришлось.

Так вот мне кажется, что использовать это не слишком удобно. В плане той же навигации по большому проекту и вникания в его структуру. Но могу ошибаться.

По мощности вопросов нет=)

forCe ()

Пока что лучше, чем в OCaml — не видел.

Miguel ★★★★★ ()

Внезапно node.js. Язык какаха, но модули запилили великолепные.

A1 ()
Ответ на: комментарий от hateyoufeel

Намудрили они в этом backpack, уж простоты-то и нету. И кстати, всё равно уши ML оттуда торчат.

anonymous ()

Мой идеал: https://docs.racket-lang.org/guide/modules.html

Должны ли модули быть файлами(один файл - один модуль) или они должны декларироваться в коде? Или это можно как-то скомбинировать?

Каждый нормальный файл — модуль. Но в файле может быть несколько подмодулей. При большом желании можно размазать модуль на несколько файлов через include.

Должен ли модуль быть единицей инкапсуляции или же это лучше оставить системе типов(классы в случае ООП)?

Должен. В C++ не зря придумали namespace

Должен ли модуль формировать пространство имен? Как должен работать import?

Должен формировать. При импорте имена можно добавлять как есть (все; списком; все, кроме списка), добавлять с явным префиксом, добавлять с переименованием отдельных имён.

Нужно ли явно отделять интерфейс модуля от реализации в коде(или даже их стоит по разным файлам разносить)?

По разным файлам — не стоит. Интерфейс выделять обязательно, чтобы прочитав шапку модуля было понятно, что именно он экспортирует.

Как система модулей сочетается с системой пакетов/зависимостей? Как сочетается с системой сборки?

В идеале модуль = пакет, import = зависимость, а система сборки должны это всё понимать.

monk ★★★★★ ()

Некий мужик по фамилии Кнут говорит что это все не важно, как только ты освоил литературный подход.

loz ★★★★★ ()
Ответ на: комментарий от loz

Литературный подход хорош, если ты пишешь книгу о программировании или какой-то исследовательский проект. В живом большом проекте это работает плохо.

anonymous ()
Ответ на: комментарий от anonymous

Ты пробовал? Вобще-то, должно быть только лучше.

loz ★★★★★ ()
Ответ на: комментарий от anonymous

Язык модулей ML

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

anonymous ()
Ответ на: комментарий от anonymous

Нужны вложенные модули.

Модуль как бы по определению является некоторой единицей системы. Зачем усложнять эту концепцию? Лучше сделать нечто вроде коллекций модулей, т.е. что-то в духе пакетов питоновских.

anonymous ()
Ответ на: комментарий от Miguel

Пока что лучше, чем в OCaml — не видел.

А чего там такого хорошего?

anonymous ()
Ответ на: комментарий от monk

Интерфейс выделять обязательно, чтобы прочитав шапку модуля было понятно, что именно он экспортирует.

Т.е. как в паскале с его interface/implementation? По мне так уж лучше заголовочные файлы...

В идеале модуль = пакет, import = зависимость, а система сборки должны это всё понимать.

А внешние зависимости как разруливать? А как указывать нужную версию пакета/модуля?

anonymous ()
Ответ на: комментарий от loz

Вобще-то, должно быть только лучше.

Почему?

anonymous ()

В Ада и Модулах то-же хотели идеальных модулей - к чему это привело не так важно по сравнению с тем, что пользоваться было практически невозможно.

Как минимум в модулях д.б. поддержка пользовательской мета-информации (как в модулях erlang - кстати, там реализовано недурно в общем и целом...)

Сишные заголовки, интерфейсы сильно рулят ...

anonymous ()
Ответ на: комментарий от anonymous

Потому что в подробной документации с кусками кода проще разобраться, чем в голом коде.

loz ★★★★★ ()
Ответ на: комментарий от loz

Но прежде чем начать разбираться в коде, нужно разобраться в структуре проекта. Ты же не будешь просто брать и «читать» весь проект. Тебе нужно понять его структуру и постепенно осваивать проект по ходу работы с ним.

anonymous ()

Мне нравится модель инкапсуляции Swift - RO/RW-доступ можно разрешить данному модулю (все файлы проекта), данному файлу или кому угодно.

Deleted ()

Как должен работать import?

Я считаю, что он должен давать возможность импортировать пространства имён с их реальными именами, отдельные функции с опциональным задаваемым именем. Что-то вроде:

import SomePackage
import SomeOtherPackage.SomeFunction
import SomeOtherPackage.SomeFunction as FooFunction

func Bar() {
   SomePackage.CallAlice()
   SomeOtherPackage.SomeFunction()
   SomeOtherPackage.CallBob() // ошибка
   FooFunction()
}
Вещи вроде using namespace blah; приводят к коллизиям идентификаторов и, следовательно, не нужны.

Ещё мне кажется нужным иметь вкладываемые пространства имён, которые выстраивались бы в иерархию с единым родителем в виде имени модуля.

Deleted ()
Последнее исправление: romeo250501 (всего исправлений: 1)

По-моему, в питоне сделано идеально в этом плане. Отдельными файлами, должен быть единицей инкапсуляции и формировать пространство имён, импорт двумя способами.

Я не вижу недостатков в том, как модули реализованы в питоне и не знаю, что именно в этом плане там можно улучшить.

Psych218 ★★★★★ ()
Последнее исправление: Psych218 (всего исправлений: 1)
Ответ на: комментарий от anonymous

Ну лол, если ты что-то знаешь про ЛП, то вобще-то, там так и пишется весь исходник, чтобы было понятно что происходит, и везде делаются перекрестные ссылки на части документа.

loz ★★★★★ ()
Ответ на: комментарий от loz

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

Так именно это и неудобно, поскольку структура проекта совершенно не ясна. А читать все совершенно ни к чему.

anonymous ()
Ответ на: комментарий от anonymous

Так именно это и неудобно, поскольку структура проекта совершенно не ясна

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

loz ★★★★★ ()
Ответ на: комментарий от anonymous

Неоправданная сложность

Да где ж там сложность-то

Смысла возиться с функторами нет

Так никто не неволит, можно подумать, что функторы каким-то образом усложнили систему модулей и вообще «не пришей кобыле хвост».

покрывается дженериками

Хорошая шутка. Ха-ха.

А не наоборот? Может это дженерики - ненадёждый и уродливый костыль для тех «современных» языков, создатели которых не осилили понять функторы.

anonymous ()
Ответ на: комментарий от anonymous

Т.е. как в паскале с его interface/implementation?

И в паскале и в лиспе и в хаскеле. А в чём недостаток/неудобство? Единственное, что я вижу — необходимость дважды указывать имя. Но в заголовочных файлах та же проблема, а без повторного указания (оберон или defun/export в CL) возникает проблема как быстро найти список экспортных объектов, не запуская библиотеку.

По мне так уж лучше заголовочные файлы...

Чем лучше? Заголовочный файл может не совпасть с объектным файлом и про это узнаешь только при выполнении.

При желании можно сделать «заголовочный модуль»

;; implementation-module;

export all-defined

...

;; module
import implementation-module;
export foo, bar, baz; // здесь экспортируем то, что надо

monk ★★★★★ ()
Ответ на: комментарий от anonymous

А внешние зависимости как разруливать?

Также. Какая разница, где лежит модуль/пакет?

import lib/net/tcp
import lib/net/tcp:1.0
import github://user/package

А как указывать нужную версию пакета/модуля?

Версия имеет смысл только в хранилище с версиями (CPAN, CTAN, cabal, ...). Для них можно сделать синтаксис типа lib/net/tcp:1.0. Или если хранилищ несколько, то lib://base.host.org/net/tcp:1.0

monk ★★★★★ ()
Ответ на: комментарий от anonymous

в модулях д.б. поддержка пользовательской мета-информации

Можно подробнее?

forCe ()
Ответ на: комментарий от monk

Но в файле может быть несколько подмодулей

А какая в этом практическая польза?

При большом желании можно размазать модуль на несколько файлов через include.

Не является ли такое желание признаком того, что модуль стоит разделить на несколько?

В идеале модуль = пакет, import = зависимость, а система сборки должны это всё понимать.

Не уверен, что это удобно на практике. Возможно, все таки нужен какой-то конфиг для пакеты/сборки. А автоматическое построение зависимостей можно сделать и в этом случае.

С остальным вроде согласен=)

forCe ()
Ответ на: комментарий от loz

Некий мужик по фамилии Кнут говорит что это все не важно

Можно пруф, где он критикует модули?

как только ты освоил литературный подход.

Не ортогонален ли он модулям?

forCe ()
Ответ на: комментарий от Psych218

Да, мне питоновские модули так же удобными кажутся. Но мнения-то есть разные.

forCe ()
Ответ на: комментарий от forCe

Можно пруф, где он критикует модули?

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

Не ортогонален ли он модулям?

Нет, ты можешь использовать то что есть в языке, а можешь не использовать. Как тебе удобно.

loz ★★★★★ ()
Ответ на: комментарий от anonymous

можно подумать, что функторы каким-то образом усложнили систему модулей

Ну вообще-то так и есть. И если особого профита в реальной жизни нет, то...

Может это дженерики - ненадёждый и уродливый костыль

Ну не стоит так перегибать. Параметрический полиморфизм таки полезная штука. И польза несколько более очевидна, чем от функторов.

для тех «современных» языков, создатели которых не осилили понять функторы.

Я думаю, что создатели языков прекрасно знакомы с этими концепциями. Но они принимают решения, основываясь на задачах разрабатываемого языка и неизбежных компромиссов на которые приходится идти.

forCe ()
Ответ на: комментарий от loz

Просто с ЛП подходом абстракции нижележащего языка не очень важны.

Есть опыт использования на практике?

forCe ()
Ответ на: комментарий от forCe

Пара хелловордов и тестовых заданий на работу.

loz ★★★★★ ()
Ответ на: комментарий от forCe

можно подумать, что функторы каким-то образом усложнили систему модулей

Ну вообще-то так и есть. И если особого профита в реальной жизни нет, то...

Где же вы там все сложность видите.

Параметрический полиморфизм таки полезная штука. И польза несколько более очевидна, чем от функторов.

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

прекрасно знакомы с этими концепциями

Так что я бы не был столь категоричен. Обычно выясняется, что «чукча не читатель, чукча архитектор языка».

По мере того, как подобные «архитекторы» самообразовываются, в язык стягивается всё больше и больше мусора и костылей, а старые костыли выпиливаются, ломая обратную совместимость.

Например, мало с чем сравнимое удовольствие читать переписку Гвидо и его аргументы в пользу тех или иных решений, зная, что за несколько лет до этого он приводил аргументы против. Или наоборот.

anonymous ()

О, ты очень хорошо выкристаллизовал тот вопрос, над которым я бьюсь.

Я сначала думал, что нет большого труда придумать годную систему модулей. Лишь со временем я увидел, какая тут раскрылась бездна, грабль полна.

Мне нужна не идеальная система (идеального не бывает), а хотя бы хорошая.

Вставлю свои 22 копейки.

Модуль может быть единицей образования системы, т.е. способом разделения системы на более простые части. Здесь важным моментом является сокрытие данных. В лиспе нет сокрытия данных, и это придаёт программисту мощь Бога (с соответствующей ответственностью). Я против сокрытия данных, потому что некоторые вещи с чужим кодом иначе просто не получится сделать. Но другие люди в других ситуациях могут с полным правом выступить против таких чудес.

Если мы считаем, что сокрытие нужно и если модуль скрывает данные, то он в этом отношении конкурирует с классом. Это кажется мне плохим делом, т.к. получается избыточность и неоднозначность в языке. Есть ли решение этого вопроса в языках с классами? Я не знаю.

Модуль может быть (и является в Паскале) элементом системы сборки - это определяет порядок сборки. Сборка Дельфи - самая удобная. Достаточно добавить файл в проект и почти никаких проблем не будет (хотя на самом деле будут проблемы с исполняемым кодом в разделе initialization и finalization модулей, которые мы не в праве менять - не хватает initialization uses и finalization uses, подобно interface uses и implementation uses для определения порядка загрузки и выгрузки).

У модуля всегда есть две части - интерфейс (чтобы с ним можно было слинковаться) и реализация. У каждой из них частей может быть свой набор зависимостей. В Паскале и С такое встречается сплошь и рядом, а в лиспе нельзя задать два набора зависимостей. Моё мнение - нужно иметь возможность задавать два (а то и три, а то и четыре) набора зависимостей для каждого этапа обработки сборочных модулей. И инструменты (компилятор) должны хотя бы частично автоматизировать поддержку правильных списков зависимостей.

Теперь мы видим, что модуль - это как минимум два различных понятия - модуль как единица декомпозиции системы - «системный модуль» и модуль как единица сборки - «сборочный модуль». Эти понятия близки, но не идентичны. Хорошая система модулей должна как-то учитывать разницу, но такие системы мне неизвестны.

Сборочый модуль имеет «абстрактное» имя, но состоит из конкретных файлов. Библиотеку можно поставить в разное место на диске, а имя модуля будет одинаково. Т.е., система управления модулей решает вопрос разрешения имён сборочных модулей в имена файлов. Этот вопрос имеет большое практическое значение. Также на распределение ответственности между разработчиками, поскольку библиотека - это может быть набор модулей.

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

Вообще-то они ортогональны понятию модулей, но в реальной жизни они, как правило, взаимосвязаны. Логично, чтобы были лучше подслащены те модули, которые близко родственны данному. А те, от которых наш модуль не зависит, должны быть намазаны дёгтем. В Дельфи вообще невозможно вызвать функцию из модуля, который нам не родственник (разве только через RTTI). Т.е., отчасти отношения пространств имён могут быть выведены из отношений зависимости системных и/или сборочных модулей.

В лиспе они ортогональны - это создаёт много гибкости (это хорошо) и много лишней работы (это плохо). Однако пространства имён в лиспе не имеют отношения к системным модулям.

Я не работал с языками, где пространства имён меня бы устроили, но я собираюсь реализовать такой язык.

В С++ класс тоже создаёт пр-во имён. Когда пишешь метод, можно ссылаться на поля класса без префикса. Мне кажется, что это достаточно удобно, но не все разделяют это мнение и входит в моду не пользоваться этим пр-вом имён. Так что модуль и класс конкурируют и здесь.

Дальше есть вопрос - плоские модули или иерархические?

Из того, с чем мне приходилось работать много - это Дельфи и Лисп. В меньшей степени - С и С++. Ну и совсем по мелочи tcl и Mathematica.

Модули и файлы - я думаю, что и системный, и сборочный модуль обычно бывает намного крупнее, чем удобный размер файла (в моём понимании норма - это 100-1000 строк). Так что напрашивается решение отделить понятие модуля от понятия файла. Это решение сильно усложняет систему. Не знаю, как здесь найти золотую середину.

И наконец, горячая замена кода - она тоже может быть взаимосвязана с модулями (а может и не быть).

В Дельфи я вижу сборочные модули, но толком не вижу системных модулей.

C/С++ все ругают, но я не вижу проблемы соблюдать дисциплину. Тогда .h является интерфейсом модуля, а .с - его реализацией. Получается достаточно неплохо. Думается, что .h воплощает в себе и системный, и сборочный модули и в целом система неплохая (пока дисциплина соблюдается). Кроме того, только в С решена проблема распространения closed source.

В Лиспе есть системные модули, они же сборочные, хотя asdf, которая это реализует, до сих пор кривовата. Плохо то, что нет отделения интерфейса от реализации и нельзя декларировать зависимости. Отсутствие сокрытия данных поощряет к нарушению границ системных модулей. Лично мне не удаётся поддерживать дисциплину и мой код имеет тенденцию становиться монолитным. Язык не представляет средств контроля неявных зависимостей на этапе сборки. С другой стороны, ортогональность модулей, файлов и пространств имён увеличивает объём работы, это сильно раздражает.

Тут говорили про Racket, Ocaml, Хаскель и Эрланг. Всё это круто, но эти языки по распространённости являются маргинальными. Любой маргинальный язык магинален не сам по себе, а ввиду каких-то ошибок. Нужно в каждом случае прояснять, не в модулях ли ошибка? Только после этого можно говорить о качестве системы модулей в этих языках.

Из популярных языков остались не охвачены Питон и Java - на них я не работал и ничего сказать не могу.

Горячей замены в С её по сути нет. В лиспе горячая замена плохо стандартизована, но фактически в лиспе реализована единственная практически полезная модель горячей замены. Замена осуществляется на уровне одного определения. Зависящие определения нужно перезагрузить руками. Это трудоёмко, зато это почти всегда возможно, потому что есть свобода выбирать, что именно заменить и когда. Благодаря этому в лиспе действительно есть горячая замена кода, хотя есть и множество способов выстрелить себе в ногу. asdf здесь оказывает значительную (хотя и кривоватую) поддержку, позволяя в какой-то степени перезагружать код в масштабе модулей.

Примерно то же реализовано в SQL, хотя в известных мне реализациях из-за транзакций горячая замена слишком ограничена и не всегда реально применима.

В Питоне есть горячая замена модулей, не знаю, как она работает и насколько хороша.

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

den73 ★★★★★ ()
Ответ на: комментарий от forCe

Я думаю, что создатели языков прекрасно знакомы с этими концепциями. Но они принимают решения, основываясь на задачах разрабатываемого языка и неизбежных компромиссов на которые приходится идти.

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

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

anonymous ()
Ответ на: комментарий от anonymous

Грубо говоря, чтобы и обезьяна смогла им пользоваться

Для индустрии это хорошо, масса взаимозаменяемых «типа программистов». А для информатики как науки? Очевидно, нет. Это деградация.

Так что «идеальная система модулей» для кого. Для обезьянок - одно, они от Питона с Руби в восторге будут, а Ява - так это вообще оргазм.

anonymous ()
Ответ на: комментарий от anonymous

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

forCe ()
Ответ на: комментарий от forCe

не совсем понижение планки, а скорее понижение порога вхождения

По мне так одного поля ягоды. Понижают порог настолько, чтобы далёкие от информатики люди могли программы кодить, не приходя в сознание.

опытным пользователям языка

Вот это очень точный термин. Не для «программистов, использующих язык», а для «пользователей языка».

А насчёт золотой середины согласен. Но на практике же всё равно чем-то жертвуем, либо «идеальностью», либо «пользовательской базой». Идеальные системы - это ближе к ML, популярные - для неискушённой публики и рак щука.

anonymous ()
Ответ на: комментарий от anonymous

Понижают порог настолько

Чтобы люди могли начать кодить на данном языке без проблем. Это могут быть вообще начинающие программисты или программисты со знанием других языков.

forCe ()

В Go идеальная система модулей

Zaskar ()
Ответ на: комментарий от anonymous

Намудрили они в этом backpack, уж простоты-то и нету

Он решает чуть более специфическую задачу, чем модули в ML. Для простого полиморфизма есть тайпклассы.

И кстати, всё равно уши ML оттуда торчат.

Этого никто и не скрывает.

hateyoufeel ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.