Михаил Бахтерев @mikhanoid
ИММ УрО РАН
Information
- Rating
- Does not participate
- Location
- Екатеринбург, Свердловская обл., Россия
- Registered
- Activity
Specialization
Backend Developer, Научный сотрудник
Applied math
System Programming
Machine learning
Compilers
Scheme
C
Assembler
Linux
Clojure
Haskell
Оба подхода заставляют по форме (что должно быть) определить содержание (как должно быть). Вобщем-то вполне себе математичный подход. Теоретически красивый. Да, мы вот сейчас всё пишем и пишем, и на словах, действительно ООП гораздо красивее обычного структурного подхода. Кроме того, можно кучу книжек писать, ООП - богатая среда для мудрствований, например, вот те же замечательные философствования о шаблонах и рефакторинге. Так же как и функциональное программирование, а когда оно ещё и с монадами, то вообще, ууУУух как поумничать можно и статьи пописать.
C же освобождает от необходимости проектировать строго определённый интерфейс, конечно, от ответа на вопрос: что код должен делать? уйти вряд ли удасться, но ответ этот может быть достаточно нестрогим. Язык ориентирован на работу с данными, а не объектами. Если я совершу ошибку на этапе придумывания интерфейса, то мне никто не мешает напрямую залезть в структуру данных, и написать новый код для работы с ней, в том месте, где он нужен. Да, это грязно, это не математично, это нарушение кучи принципов, об этом не напишешь умную книжку, потому что это глупо, но это работет. И работает не только корректно, но и эффективно. Если подобный код мне нужен будет во многих местах - родилась абстракция - я его вынесу в соответсвующий модуль, и доступ к нему сделаю частью интерфейса.
Мог бы я подобное обнаружить на бумажке? Запросто, если бы писал на бумажке настоящую программу, до уровня операторов и вызов функций, а не просто рисовал бы стрелочки, то наверняка обнаружил бы то же самое. Но на бумажке невозможно написать нечто более или менее сложное.
Я могу предполагать, что в этом случае списки эффективны, но на реальных данных может оказаться, что они убивают производительность, или, вдруг оказывается, что для реализации некоторой возможности в новой версии нужно обращение к произвольному элементу. Если я это всё буду учитывать только после того, как напишу программу на бумажке, потом закодирую на ООП, потом откомпилирую, потом пойму, что все мои расчёты, мягко говоря, некорректные, потом опять сяду за бумажку... Я рискую ни одной эффективной программы за всю жизнь не написать.
В итоге, конечно, у меня тоже появляются объекты в программе, но границы у них не жёсткие, а размытые, и объекты именно появляются, а не спускаются сверху. Не тотальная закапсулированность позволяет упростить взаимодействие, особенно, при использовании в новом коде. А то, что объект возник естественным путём освобождает меня от необходимости прописывать какую-то невостребованную функциональность.
Да, всё то же самое можно делать и на C++, например. Но тогда надо будет делать все данные и методы изначально открытыми, и возникает вопрос, а зачем тогда этот весь этот лишний классовый синтаксис?
Ещё можно сказать, что в Smalltalk именно так формировались интерфейсы. Они формировались снизу вверх. Они возникали, а не навязывались сверху. Наследования же вообще не было. Все эти, на мой взгляд, сложности, связанные с проектирование сверху вниз, появились в компилируемых языках, в которых для повышения эффективности стали путать типы и объекты.
И последнее замечание: в книжках по функциональному программированию, обычно, всё истолковывается в терминах и примерах из императивных языков программирования, поэтому, в них со сравнением с другими технологиями всё лучше.
И я должен буду выполнять много сложной работы по переписыванию кода, настолько сложной, что для этого появляются автоматизированные средства.
Но для новых проектов всё это ООП - зло. Оно заставляет поиск решения производить задом наперёд: придумали абстракцию начали делать, а потом, хопа, поняли, что всё это работать не будет, и начинается изменение всей иерархии. Это же сложно и время отнимает, поэтому люди начинают нарушать парадигму на право и налево. Но возникает вопрос: а зачем тогда вообще они начали писать свои классы?
Насчёт free. Частое использование malloc/free - это рузультат неправильного проектирования, по моему мнению. И является болезнью больших проектов, в которых писают всё написать на C. Но C придумывался не для этого. Компоненты, написанные на C должны быть маленькими и максимально специализированными под решаемую задачу. Большая же решается через объединение компонентов через IPC. Это удобнее, чем придумывание иерархий, потому что позволяет вклиниваться и менять взаимодействие между компонентами как угодно.
Попробуйте воткнуть дополнительную обработку в pipe, или дополнительную обработку во взаимодействие между двумя объектами, когда один вызывает другой. Как минимум, придётся нечто наследовать, заново думать над тем, что должно быть виртуальным или не виртуальным. И так далее. Много работы. Может быть, именно поэтому, все сложные ОО системы рано или поздно приходят к тому, чтобы создать свою транспортную систему данных между объектами. Или воспользоваться существующей в виде базы данных или чего-нибудь, вроде, CORBA. Но это всё усложнения, усложнения и ещё раз усложнения. Которые надо осваивать, особенно при коммерческом программировании, а это всё уводит сознание от размышлений над другими методами организации вычислений. Существенно более гибкими и простыми. Ну вот сравнить хотя бы ту же CORBA с plumber'ом из Plan9 или Linda.
Поэтому я против ООП. Мне не нравится, когда навязываются сложности и неэффективности, да ещё такими ненаучными способами: вы хотя бы в одной книге по ООП видели сравнение предлагаемых способов решения тех или иных иных задач в этой парадигме с решением их в других?
Удручает только то, что вместо, например, огромной библии программирования на с++ или толстенной camelbook он бы мог прочесть тоненькую книжку о 'c', и толстую книжку об алгоритмах и структурах данных, да пару руководств по shell с описанием всяких утилит, вроде sed, что позволило бы ему писать более эффективно и быстро, да к тому же использовать все возможности существующего кода.
Почему сложные языки популярны? Популярность этих средств книжна: чем толще книжка, тем язык круче - обычная мораль, обычного самца homo sapiens. Раз он смог освоить очень сложное средство, то он крут. Раз он смог освоить корявое средство, вроде C++, то он крут. Так уж получилось, что люди с деньгами оценвают не эффективность, а именно крутость того, кого нанимают, ибо в эффективности они понять ничего не могут, в силу иной профессиональной ориентации. А крутость, ещё раз, в программировании это означает умение программировать на сложных средствах. Нет, конечно, гораздо круче разбираться во всех алгоритмах, упомянутых Кнутом и Корнелом, но для этого уже нужно напрягать мозг и тратить время, и проверить эту крутость сложнее, потому что тогда наниматель должен в этом всём разбираться сам, а там терминов и абстракций гораздо больше, чем в семантике языка программирования, а связи между ними гораздо богаче, чем отношение isA.
Плюс людям с деньгами на семинарах промывают мозги, рассказывая сказки о том, насколько Java может быть эффективной, и насколько хорошо она может управлять нитями, и насколько хорошо памятью. Но начинаешь разбираться и: конечно, может, иногда, в некоторых случаях, специальных, которые часто встречаются в том коде, скорость работы которого не критична. Вы не забывайте, что насколько бы продвинутой была VM она обязана защитить runtime от действий программиста, а это горы лишних проверок при хоть сколько-нибудь нетривиальном обращении к массивам, или отсутсвие нормальных массивов в языке совсем. Но менеджер этого не знает, а обёртка у Java очень красивая.
Про C++ то же самое: смотрите какие чудные иерархии мы можем строить, а потом использовать совершенно безумные хаки, чтобы это всё нормально работало, cooool, давайте заставлять их строить всегда и везде, в любой беде. Давайте напишем про это кучу теоретических книжек, сделаем большие бабки на этом, и дадим кодерам возможность получать больше денег, за меньший объём осмысленной работы. Умные гомоморфные автоматические указатели со сборкой мусора рулятЪ и trueЪ. Это же такая хардкорная математика, и, как алгебраиста интеллектуально она меня очень привлекает, например. Всегда очень хочется прочитать новое издание advanced c++ programming. Но это всё красивые теории, а не практика.
Это всё к удобству и эффективности отношения не имеет. Просто, подкреплённое ленью менеджеров и ценой на компиляторы (компилятор C++ или C# НАМНОГО сложнее, чем компилятор C, естественно, их сложно воспроизвести, следовательно, на этом можно делать деньги), общественное мнение и работа на собственную крутость.
Между тем, на C в комбинации с продвинутой shell можно спокойно писать сложные web-приложения. И даже гораздо более сложные, чем на всех этих высокоуровневых С#, Java, C++, Python, которые загоняют программистов в древовидные структуры классов и библиотек. Потому что дают свободу. Посмотрите на все web службы для Plan9 - там только C и rc. Сложный ли там код? Гораздо проще, чем мне доводилось видеть в аналогичных службах, писанных на asp.net. Да, на C приходится писать free, освобождая память, но это единственная техническая сложность. Но по сравнению с чисто техническими проблемами при использовании классов - это такие мелочи.
Почему Linux'оиды чаще всего выбирают для программирования C и shell, а не кучу других языков, которые доступны для использования? У меня вот в gentoo установлено сейчас около 400 пакетов, и только около 50 составлены на чём-то отличном от C и shell. Не по историческим причинам совсем при разработке 'софта не из-за денег' используется именно эта комбинация, есть много свеженьких проектов, разработку которых начали в эпоху всего этого высокоуровневого разгула. Именно потому, что это достаточно компактные языки. Можно быстро выучить и запомнить все странности и глюки, можно получить свободу в оперировании данными. А это как раз то, что нужно, чтобы за короткое время стартануть, а потом эволюционно развивать проект.
Поэтому, возможно, самого C не станет со временем, но подобный ему язык со всеми прелестями (действительно прелестями, а не недостатками, как это считают теоретики), вроде указателей и плоского пространства имён, обязательно будет существовать. И будет массовым. Потому что простенько, эффективно и пригодно не только для того, чтобы писать ядра систем или виртуальные машины для языков программирования.
Но они погоняют свои мелкие тесты, вроде фотошопов, кодеков и архиваторов, и опять объявят процессоры самыми лучшими и высокопроизводительными на рынке PC. Ну ладно, они будут правы, Intel они опять сделают, скорее всего. НО, можно же более производительную архитектуру предложить, как показывают все эти эксперименты. И более дешёвую и энергоэффективную, как доказывает IBM своим BlueGene.
Хм. Неужели единственное, что сдерживает - страх перед тем, что программисты не освоят программирование для логически неоднородной системы? Хотя, тут давление, наверное, ещё со стороны компиляторщиков, которым надо продавать продукты с этикеткой OpenMP compatible. Да и Windows исторически многонитевая и мозги ей уже ничем не вправишь, кроме лома. Мдя, опять система ползёт в глючный аттрактор, как это произошло с x86.
Ну, хорошо, у всех свои интересы и так далее. Я понимаю это. Но я не понимаю, от куда негативное отношение к велосипедостроению : ) То есть, люди делают попытки доказать, что отношение велосепедиста к жизни не правильное.
Вот вчера был свидетелем, как солидный специалист в одной закрытой общалке, двум студентам промывал мозг на тему того, что ну не правы они, раз хотят свой движок для игр писать, нужно обязательно изучить движок Unreal и не шалить самостоятельно. Хм... Вот так вот. Нужно быть очень энергичными, чтобы не пасть духом и не разбиться о стену существующего.
gcc foo.c -o bar
На это можно посмотреть, как на
1. Логический вывод: существует утверждение gcc, утверждение foo.c из которых можно получить вывод - bar. Вобщем-то давно известно, что программы соответствуют выводам в интуиционистской логике. http://en.wikipedia.org/wiki/Curry%E2%80…
Это, конечно, не поиск наименьшего общего унификатора, но Пролог немного задевает. Хотя, наверное и не так сильно, чтобы выносить это в ключевые слова : ).
2. Вызов функции bar - получение этого значения при помощи применения функции gcc к функции-константе (хотя и не обязательно совсем) foo.c
3. Обычные операции с именованными данными: загрузить программу gcc, заставить её загрузить файл foo.c и выдать файл bar
Всё описывается одинаковой абстрактной схемой: gcc, foo.c -> bar. А подобные схемы исполнять параллельно очень удобно. Параллельные haskell'ы так делают. Или, например, make на это способен. Параллельная редукция lambda-графа, или графа зависимостей. Но это можно описывать не только в виде таких вот возвышенных материй, но и в виде операций с файлами:
for ((i = 0; i < 20; i = i + 1)); do gcc foo$i.c -o bar$i & pids[$i]=$!; done
for ((i = 0; i < 20; i = i + 1)); do wait pids[$i]; done
Граф потоков данных описывается на языке зависимости файлов (на самом деле достаточно говорить просто об именах файлов. имена могут содержать и некоторую версию) друг от друга. Ну, на shell для этого нужен wait для процессов, которые формируют файлы, или использовать какую-нибудь самопальную следилку, работающую через inotify.
В специально заточенной системе нужна файловая система, которая хранит эти файлы по возможности в памяти, позволяет отслеживать создание новых файлов, следит за уникальностью их имён (вобщем ничем особо экстраординарным по сравнению с обычной fs не занимается), чтобы потом можно было вычисления по мере готовности данных запускать. Программку запустили, выдали ей набор файлов для чтения, указали, куда результаты положить, результаты же потом можно использовать в других процессах - всё просто, привычно скриптуется и так далее. Остаётся только эффективности добится в доступе к самим данным, но тут ничего принципиально сложного, кажется, не возникает.
Сравнение с логикой: как только доказано, что a, b, c, d (сформированы соответствующие файлы) в утверждении a, b, c, d -> e, можно считать доказанным e (запросто формировать e). Поиск же доказательства или модели, удовлетворяющей аксиомам можно осуществлять параллельно. При этом очень даже совсем неплохо параллельно - тысячи математиков примерно этим заняты. Так почему бы эти конструкции вида a1, ..., an -> b1, ..., bn - предложения Хорна - из логики не использовать в описании алгоритмов, если ко всему прочему доказано, что они Тьюринг-полные?
Сравнение с функциональным программированием: речь идёт о банальном ленивом вычислении значения. Только не на уровне базовых конструкций функциональных языков, а на уровне файлов с произвольным содержимым. Haskell, как shell : ) Никто ещё не пробовал?
Вот. При этом всём файлы достаточно удобны при числодробительных вычислениях - это некие куски данных с произвольным доступом. Как раз то, что нужно чтобы матрицы умножать. Гораздо удобнее списков или небезопасных массивов из мира функциональных языков. Да и просто привычнее.
Ещё. Эти штуки сложно описать с использованием понятия 'переменная'. Но понятие 'переменная' достаточно просто выражается через понятие именованных данных (не областей памяти, привязанных к железяке, а именно просто данных) - как последовательность значений. Кроме того, 'переменная' вообще плохо параллелится, потому как это именованная ячейка и к ней нужно доступ синхронизировать, или копировать их, чтобы конфликты разруливать.
Вобщем примерно всё вот так. Подробнее, конечно, надо. Но времени нет : (