Главная » 2016 » Июнь » 20 » RB32-1. Ортогональная система языков в проекте "Роса"
13:13
RB32-1. Ортогональная система языков в проекте "Роса"
Предпроектные исследования в рамках проекта "e;Роса"e; велись по двум основным и взаимосвязанным направлениям: (1) инструментарий (языки) и (2) операционная система, с выделением в особую ветвь связующей основы, связанной с архитектурой и инженерией программных систем (чему мы дали термин "e;метасистемное программирование"e;). В этой заметке я постараюсь изложить контуры подходов к языкам. Почему языки реализации так важны? Когда мы занялись вплотную проектом новой ОС, возникли сомнения, нужно ли уделять пристальное внимание отбору языков программирования. Ведь вроде бы главное - архитектура ОС, а на чем её делать - не столь важно. Это острый момент, по которому у нас разгорелись бурные дебаты. Вопрос сводился к тому, важен для ОС язык реализации или же всё равно какой. Как выяснилось, сторонники UNIX склоняются больше ко второму варианту. И утверждают: в итоге мы придём по архитектуре к UNIX, реализованной на Си. Моё возражение было простым - замените Си, например, на классическую Модулу-2 и вы увидите наглядно существенную разницу в качестве и надёжности системы не в пользу нынешних реализаций. Но зачем нам вообще переписывать готовую архитектуру на другом языке? Мы собрались не для того, чтобы побыстрее абы как закрыть пробоину. Мы делаем систему нового поколения, с перспективой на многие годы вперёд, отталкиваясь от осознания серьёзных промахов современного программирования, которое в угоду коммерциализации проигнорировало весьма важные идеи прошедших десятилетий. При этом, понимая, что в группе собрались сторонники диаметрально противоположных воззрений на ОС (UNIX и Oberon) с самого начала постулировалась аксиома - новая операционная система не должна быть ни реинкарнацией ОС UNIX-семейства, ни реинкарнацией ОС Оберон-семейства, хотя отдельные решения оттуда могут и должны быть использованы. На первом направлении давно уже настоящее столпотворение. Там справятся и без нас. Особенно с учётом ставки на тотальную линуксоизацию населения. На втором направлении - ярко выраженный технологический изоляционизм: монопольный маргинальный язык со спартанской операционной средой (сращивание исполняющей системы языка и ядра ОС). В Оберон-системах есть немало интересных решений и находок, но в общем и целом это, скорее, исследовательский полигон, имеющий врождённые проблемы. Нужно особо отметить, что мы декларировали приверженность системному подходу, рассматривая ОС, прежде всего, как сложную программную систему и продумывая новые горизонты, которые открывает метасистемное программирование (в нашем понимании). Здесь весьма показательна такая точка зрения: для системного подхода всё едино, на всём можно строить с одинаковым успехом. На мой взгляд, это типичное мнение архитектора. И не только программных систем. Инженеры прекрасно понимают, что надёжность придуманной конструкции обеспечивается не в последнюю очередь строительным материалом, а также знанием сопромата. В сфере ОСестроения понемногу приходит понимание, что язык (строительный материал) - не последнее дело. И что учитывая его, можно избавиться от многих проблем, от которых архитектура не спасает (пресловутая уязвимость переполнения буфера, принцип языковых процессов в едином адресном пространстве -Xerox Cedar, Oberon System, ETH Bluebottle, Microsoft Singularity и др.). Итак, выбор базового языка важен. В любой ОС есть "e;три слона"e;, на которых она покоится: (1) исполнение программного кода, (2) управление активностями (процессами) и (3) распределение ресурсов. Память является ключевым элементом, той самой гигантской черепахой, на которой в моделях мироздания древних и располагались три слона. От того, сколь грамотно будет продумана организация памяти, содействующая эффективному и надёжному исполнению основных языков ОС, зависит многое. Сколь сильно она должна от них абстрагироваться? Окажется ли полезным для возведения ОС знание специфики работы с памятью в том или ином языке из очерченного нами набора? Другим ключевым элементом является процессорная архитектура. Сколь оптимально может быть адаптирована к ней ОС с учётом взрывного роста разработок в этой сфере и приоритетом развития многоядерных процессоров? Какими средствами виртуализации можно снизить зависимость программного обеспечения от железа? Контроль сложности - основная проблема программирования Мы идём не лобовым путем: вместо ставки на популярные языки занимаемся разработкой и реализацией своих, что также вызывает немалые вопросы и сомнения. Зачем делать какой-то инструментарий, когда можно пользовать готовое? Вполне типичная точка зрения. Давайте попробуем разобраться. Приведу небольшую выдержку из книги известного советского математика, одного из зачинателей отечественного программирования Александра Семёновича Кронрода "e;Беседы о программировании"e; (1963). Она поясняет, почему столь большое внимание при создании новой ОС мы уделяем языкам и инструментарию: "e;Пусть нам нужно выкопать большой канал. С чего стоит начать? Первое, что приходит в голову,- взять лопаты и копать. И, если не думать, что можно построить экскаватор, то чем скорее возьмешь лопату и чем энергичнее станешь ею действовать, - тем быстрее и глубже будет твоя канава. Ну, а если поверить, что экскаватор построить все-таки можно? Тогда, пожалуй, стоит повременить с лопатой. И как можно энергичнее заняться экскаватором. Особенно, если копать лопатой и строить экскаватор должны одни и те же люди. И, конечно, если канава не нужна немедленно и непосредственно, во имя спасения жизни"e;. Сложность и попытка взять её под контроль - вот основной лейтмотив нашего проекта. Это и неудивительно, поскольку именно в этом и состоит, по мнению многих классиков, основная проблема программирования. Незадолго до своей кончины Эдгар Дейкстра в работе "e;Конец информатике?"e; (2000, EWD1304) сказал очень мудрые слова: "e;В то время, как мы знаем, что причиной всех бед является неуправляемая сложность, мы не знаем, ни какой степени простоты можно достичь, ни до какой степени внутренняя сложность разработки в целом должна отражаться на видимых интерфейсах. Мы попросту до сих пор не знаем, насколько сможем выпутаться из этого. Мы до сих пор не знаем, можно ли отличить сложность, присущую самой задаче, от случайной сложности. Мы до сих пор не знаем, будут ли возможны компромиссы. Мы до сих пор не знаем, сумеем ли разработать для сложности осмысленную концепцию, на базе которой сможем построить полезную теорию"e;. Пытаться подойти к решению такой грандиозной задачи исключительно простыми средствами - романтический идеализм. Но другой крайностью являются и попытки штурмовать сложность едва ли не еще большей сложностью. В 2005 г. мне довелось пообщаться с Леоном Кацнельсоном (директором IBM по конкурентным технологиям при департаменте информационного управления). Вот краткая выжимка беседы с ним. Я тогда упомянул о работе Пола Хорна (главный вице-президент IBM Research) по автономному компьютингу. В ней высказывается следующая мысль: сложность современных ИТ-систем - это серьёзный вызов. Как это ни парадоксально, чтобы избежать сложности, утверждает Хорн, надо делать ещё более сложные системы. Т. е. сложность преодолевается через сложность. Хорн подчёркивает, что следуя принципам автономного компьютинга, надо системы ещё более усложнять, но внедрять при этом элементы адаптивности. Кацнельсон отреагировал на мысль Хорна весьма дипломатично, сохранив парадоксальность: "e;Чем сложнее система, тем больше необходимость её упрощать. При этом сам процесс упрощения ещё больше усложняет эти системы"e;. Очевидно, что надо искать золотую середину. И очевидно, что всё отдавать здесь во власть человеку - не выход. Человеческий фактор чаще содействует сложности, поскольку человек привык идти по пути наименьшего сопротивления. Нужны средства, близкие к автоматическому эволюционированию систем в пределах контролируемых ограничений. Нужны средства адаптации (статической и динамической). Но вот это-то совсем не просто. Даже с учётом немалого потенциала исследовательских работ в этой сфере. Есть устойчивый стереотип: сложные задачи можно решать только сложными инструментами. Наверное, всё же корректнее говорить - адекватными, подходящими, а не сложными. Что есть простота и сложность применительно к инструменту? Скальпель - сложный инструмент? По своей сути, по своей структуре - нет. Но при его использовании включаются в действие сопутствующие знания и навыки того, кто им оперирует. Нужна высокая квалификация. Есть простота структуры, есть простота комбинирования структур (установления связей этой структуры с другими), есть простота применения инструмента (адекватного владения им для данного класса задач). Чем проще инструмент, тем при росте сложности решаемых задач более высокие требования он предъявляет к квалификации того, кто с ним работает. C++ и Оберон - два подхода к борьбе со сложностью Не секрет, что в качестве базового языка в проект новой ОС планируется наш новый диалект классического Оберона (языка проф. Никлауса Вирта). Оберон славится своей простотой и концептуальной сбалансированностью. Но в отношении планки квалификации он не исключение. Его простота сложна. Сложна для тех, кто привык сложность перекладывать со своих плеч на чужие (я имею в виду с прагматики языка на его семантику). Сложность простоты Оберона - это неизбежная плата за простоту. Язык C++ представляет собой пример иного подхода - попытки убрать сложность путём введения адекватных инструментов. Но при этом вместо одной проблемы возникают другие (сложность переносится на иной уровень и становится нередко неконтролируемой) - количество связей между базовыми сущностями и структурами (включёнными в семантику языка) начинает возрастать, они вступают в явные и неявные конфликты, распутывать которые должен будет тот, кто этот язык применяет. Вирт в Обероне шёл простым и понятным путем. Сначала выбирается область применения инструмента (цели): системное программирование применительно к разработке операционной системы Oberon (однопользовательская ОС класса Developer's OS), в которой главными требованиями являются динамическая расширяемость систем (extensible programming) при изолированности (герметичности) системы типов. Затем используется принцип концептуальной экономии (по Чарльзу Хоару): Вирт брал сбалансированный набор базовых сущностей и их отношений (связей, правил - моделью была классическая Модула-2) и удалял из языка (синтаксиса и семантики) те вещи, которые не разрушали исходные цели (целеполагание). Другими словами, искал ту грань, за которой система (в данном случае язык Оберон) будет оптимизирована по критерию концептуальной экономии и не рассыплется. На мой взгляд, этой цели он добился. Для реализации системы Oberon (Oberon System) язык Оберон выглядит близким к идеалу. Теперь посмотрим на C++. Цели, которые ставил перед собой Бьёрн Страуструп, существенно отличались от целей, поставленных Виртом перед языком Оберон. Страуструп хотел, с одной стороны, воплотить идеи своего любимого языка (Симулы-67), оставаясь на базе синтаксиса (и семантики!) Си. Т.е. обеспечить преемственность. И в этом плане с Виртом они не сильно расходятся (преемственность по синтаксису и семантике у Оберона с Модулой-2 даже куда выше, чем у Си и C++). А вот с другой - он хотел создать объёмный универсальный самодостаточный язык, который может практически всё. Поиск пресловутого философского камня. Проблема Страуструпа была в другом - он хотел насытить язык многими полезными вещами. А каков в этом случае критерий развития языка? Почему можно прямо в язык (не в библиотеки) добавить одну полезную вещь и не добавлять другую? У Вирта удаление "e;лишней"e; полезной вещи приводит к рассыпанию системы. У Страуструпа добавление "e;лишней"e; вещи не рассматривается как разрушение системы, поскольку система (язык) воспринимается сама по себе в отрыве от того, кто ею оперирует (и кто хранит в своей голове полезные и вредные связи между плодящимися сущностями языка). В этом и есть главная проблема подобных языков, стремящихся вобрать в себя побольше полезного. Нет сдерживающего фактора, кроме чувства меры (а больше вкусовщины) комитета по стандартизации (уже не самого Страуструпа, мнение которого здесь далеко не решающее). К чему ведёт такой подход? К неконтролируемому росту сложности как самого инструмента, так и сферы его практического применения. Тем, кто интересуется этой проблематикой, очень рекомендую внимательно перечитать (под призмой того, что здесь пояснил) книгу Б.Страуструпа "e;Дизайн и эволюция C++"e; (1994; пер. на русский язык - 2006). Я вообще рассматриваю эту книгу как замечательное пособие нам, участникам проекта "e;Роса"e; (где будут создаваться новые языки и диалекты). Прежде всего в отношении того, как не надо наступать на чужие грабли. Вот цитата из этой книги (с. 113-114), характеризующая процесс принятия решения в C++, когда вместо приоритета качества, технологического совершенства во главу угла ставится удобство для нынешних программистов (потакание стереотипам): "e;Пришлось принять правило Cи о том, что глобальные имена по умолчанию видимы в других единицах трансляции. Просто не было поддержки для правила, более жестко ограничивающего область видимости имён. Это означало, что в C++, как и в Cи, не будет механизма для выражения модульности на уровне выше класса или файла. Это послужило причиной многих жалоб, пока комитет ANSI/ISO не принял пространства имен в качестве механизма, позволяющего избежать непреднамеренного совмещения имен. Однако Дуг Макилрой и другие возражали, что Cи-программисты не оценят язык, где каждый объект и функцию, которые должны быть доступны из другой единицы трансляции, придется явно объявлять таковыми. Возможно, в то время они были правы и уберегли меня от серьезной ошибки."e; А вот ключевой момент, отличающий подходы Вирта и Страуструпа. Он заключен в следующей цитате (см. с.276). Привожу слова Страуструпа: "e;Множественное наследование виделось как первое существенное расширение C++. Некоторые опытные пользователи C++ считали, что это ненужное усложнение, которое повлечет за собой поток новых средств. Например, на самой первой конференции по C++ в Санта-Фе Том Каргилл сорвал аплодисменты, высказав забавное, но не очень реалистичное предложение: каждый, кто предлагает включить в C++ новое средство, должен сказать, какое из старых средств, равных по сложности новому, следует исключить. Мне такой подход нравится, но я всё же не могу утверждать, что C++ стал бы лучше, не будь в нём множественного наследования, или что C++ образца 1985 г. лучше C++ 1993 г. Веселье продолжил Джим Уолдо. Он продолжил мысль Тома, высказав следующую идею: обязать предлагающих новые средства пожертвовать... свою почку. Это, по мнению Джима, заставит каждого не один раз подумать, прежде чем вносить предложение, причём даже лишённые инстинкта самосохранения не смогут внести более двух предложений"e;. Вот в чём проблема: если кто-то пытается применять систему (язык Оберон) вне той задачи, для которой она предназначалась и оптимизировалась, то следует задуматься: виноват инструмент, его автор или же тот, кто (не понимая инструмента) пытается его применять где можно и где нельзя. Оберон - это язык-ядро, язык-чемоданчик. Это не язык-оболочка, не язык-сундук. Для использования в области промышленного производства ПО, промышленной разработки сложных программных систем, характеризующейся разделением труда при наличии большого числа программистов, язык Оберон требует соответствующей инфраструктуры, не отвергающей, а впитывающей в себя дополняющие языки (в их синтаксических одеждах). Только в этом случае он, на мой взгляд, будет адекватен решаемым задачам в этой сфере. В качестве иллюстрации к сказанному хотел бы напомнить слова великого немецкого философа Имманула Канта, которые нам стоит помнить: "e;Кто отказался от излишеств, тот избавился от лишений"e;. Сундуки и чемоданчики Языки-сундуки и языки-чемоданчики. Интересная ассоциация проф. В.Ш.Кауфмана. Думаю, имеет смысл сделать большое отступление и ввести читателя в контекст обсуждения. Я очень рекомендую тем, кто серьёзно интересуется этими вопросами (языков и их концепций), перечитать от корки до корки блестящий курс д.ф.-м.н. Виталия Шахновича Кауфмана "e;Языки программирования. Концепции и принципы"e;. - М.: Радио и связь, 1993. Вышел мизерным тиражом в 2 тыс.экз. Этот фундаментальный курс читался на факультете ВМиК в МГУ, подготовлен с активным участием С.И.Рыбина. Ныне проф. Кауфман - гражданин Финляндии. Работает в компании Fatman (Хельсинки). Ранний черновик книги Кауфмана (1986), отличающийся от печатной версии, см. http://www.europrog.ru/book/plvk1986r.pdf Вот что пишет проф. Кауфман. Далее идут выдержки из его книги. ===> Язык программирования - это инструмент для планирования поведения исполнителя... Важно понимать, что с каждым языком программирования связан эталонный (абстрактный) исполнитель, в котором, в свою очередь, определены данные, операции, связывание, именование, аппарат прогнозирования и контроля, а возможно, и аппарат исключений, синхронизации и защиты... Знания, представляемые в компьютерах, можно разделить на пассивные и активные. Первые отражают факты, связи и соотношения, касающиеся определенного вида услуг. Вторые - это рецепты, планы действий, процедуры, непосредственно управляющие исполнителем. Представление пассивного знания ориентировано в первую очередь на такой ресурс компьютера, как память, а представление активного - на процессор... Сложность - основная проблема программирования; связана с самой его природой; можно надеяться на её понижение для освоенных классов задач... Первый источник сложности в программировании - так называемый семантический разрыв - разрыв между уровнем и характером элементарных объектов и операций, с одной стороны, и потенциально возможных услуг - с другой. Иными словами, это проблема согласования масштаба - ювелирными инструментами предлагается сооружать города... Занимаясь определённым классом услуг (задач) , можно стремиться выделить характерный именно для этого класса набор элементарных объектов и операций, построить соответствующий исполнитель (аппаратным или программным способом) и программировать на таком более подходящем исполнителе. Фактически это означает создать адекватный выбранному классу услуг язык программирования. На практике это самый распространённый способ борьбы со сложностью и одновременно основная причина роста проблемно-ориентированных языков... В качестве второго источника сложности в современном программировании следует назвать незнание компьютером реального мира. Лишённый необходимых знаний, компьютер не может не только скорректировать неточно указанные в программе действия, но и проинформировать об отклонениях от направления на цель работы. Традиционное для компьютеров управление посредством указания действий, а не целей требует учета мельчайших нюансов всех обстоятельств, в которых может оказаться исполнитель в процессе предоставления нужной услуги... Важнейшим средством борьбы с семантическим разрывом служит аппарат абстракции-конкретизации, имеющийся в том или ином виде в любом языке программирования... Важнейшим средством борьбы с незнанием реального мира служит аппарат прогнозирования-контроля... Два выделенных источника сложности - семантический разрыв и незнание мира - полезно трактовать как два различных аспекта единого источника: рассогласования моделей проблемной области - области услуг, задач, операций у пользователей и исполнителей. При таком взгляде создаваемая программа выступает как средство согласования этих моделей. Чем ближе исходные модели, тем проще программа... Безнадёжно строить языки программирования с моделями, заготовленным "e;на все случаи жизни"e;. Однако можно попытаться построить язык программирования, на базе которого будет удобно (относительно несложно, с приемлемыми затратами) строить модели весьма разнообразных проблемных областей. Такой язык называют базовым языком программирования... Главное назначение базового языка - строить модели проблемных областей с тем, чтобы уменьшить сложность программирования в них. В качестве основных средств понижения сложности мы выделим абстракцию-конкретизацию и прогнозирование-контроль. Первое средство будем кратко называть аппаратом развития (так как по существу оно служит для построения над исходным языком новой знаковой системы...)... Второе средство будем называть аппаратом защиты (так как оно используется, в частности, для защиты построенных абстракций от разрушения)... <=== Далее В.Ш.Кауфман выделяет в каждом языке базис (элементарные типы - скалярная сигнатура, а также структуры данных и операций - структурная сигнатура). К нему добавляются аппарат развития и аппарат защиты. ===> На примере Ады мы видели, как выявляемые технологические потребности приводили к новым конструктам. Может показаться, что на этом пути будут получаться все более качественные языки программирования. К сожалению, большинство современных индустриальных языков программирования носят на себе родимые пятна такого примитивного критерия качества... Основной принцип конструирования, которым руководствовались авторы этих языков программирования, в упрощенной форме можно сформулировать так: для каждой значимой в проблемной области технологической потребности в языке должно быть готовое выразительное средство. Короче: каждой значимой потребности - новый конструкт. Этот принцип заготовленности конструктов и назовём принципом сундука (именно в сундуках хранят много всякого на всякий случай). Как показывает опыт, безудержное применение приципа сундука ведёт к громоздким, сложным, дорогим в реализации, обучении и использовании языкам-монстрам с тяжеловесным базисом и несбалансированными средствами развития. Сундук и есть сундук! Бывают взаимодейтствия, сложность которых по существу не зависит от собственной сложности взаимодействующих объектов... Сложность вызова процедуры непосредственно не связана с её внутренней сложностью и сложностью вызывающего контекста. В подобных ситуациях сложность инкапсулирована. Образно говоря, простота взаимодействия обеспечивается "e;небольшой площадью"e; взаимодействия потенциально весьма сложных объектов. Язык программирования сам служит "e;поверхностью"e; взаимодействия авторов, реализаторов, преподавателей и пользователей языков. Такая специфическая роль языка программирования определяет справедливость для него следующего закона распространения сложности: собственная сложность языка распространяется на все аспекты его "e;жизни"e; (описание, реализацию, использование, обучение и т.д.). Никлаус Вирт отмечает частный случай этого закона как самое главное, что следует усвоить о реализации языков программирования... Итак, ориентация на принцип сундука повышает собственную сложность языка программирования, что по закону распространения сложности приводит к росту сложности его освоения (которая, в свою очередь, может оказаться катастрофической для его судьбы - достаточно сопоставить судьбу Паскаля и Алгола-68). Вспомним, однако, что основной принятый нами критерий качества базового языка программирования - его способность снижать сложность, помогать в борьбе с основной проблемой программирования. Налицо тупик, в который ведёт принцип сундука. Вирту принадлежит принцип, указывающий выход из этого тупика. Никлаус Вирт неоднократно отмечал, что самое трудное при создании языка программирования - решить, от чего следует отказаться. Объясняя принципы конструирования своего (теперь уже предпоследнего) языка Модула-2 (поразившего специалистов элегантностью), Вирт развил эту идею и сформулировал следующий принцип языкового минимума: в язык программирования следует включать такие концепты и конструкты, без которых совершенно невозможно обойтись. Назовём этот принцип минимума принципом чемоданчика по контрасту с принципом сундука (в чемоданчик кладут только абсолютно необходимое)... Принцип чемоданчика как авторский принцип может проявиться в том, чтобы отказаться от борьбы за определённую нишу, если для этого ещё не созрели технические или социальные условия. Другими словами, принцип минимальности можно интерпретировать и так, что следует выбирать ниши, где предлагаемые языковые решения будут выглядеть как совершенно необходимые... В феврале 1988 г. стало известно о новом языке Н.Вирта - Оберон. Вирт не склонен связывать с выбором имени для своего детища каких-либо глубоких соображений, однако отмечает, что для него Оберон скорее самый крупный спутник Урана, чем король эльфов. Оберон интересен прежде всего как очередная попытка достичь идеала языка программирования, следуя принципу чемоданчика с учетом новейших достижений в философии и технологии программирования... Отметим, что целью Вирта был минимальный базовый язык програмиирования для персональной рабочей станции. Правильнее назвать требуемый язык программирования не просто базовым, а монопольным (интегрированным) языком программирования. Другими словами, язык программирования должен быть таким, чтобы ни создателю программного обеспечения станции (включая её операционную систему), ни пользователю станции не нужен был никакой иной инструмент программирования... Идея монопольного языка программирования, с одной стороны, очевидным образом перекликается с идеей единого универсального языка программирования, которая, как известно, многократно терпела фиаско и вновь воскресала на очередном этапе развития программирования. Ясно, что идея монопольного языка программирования жизнеспособнее за счёт отказа от претензий на пригодность для любых классов задач, классов пользователей, любых компьютеров и программных сред. Более того, она фактически реализована в таких языках программирования, как Си для UNIX-совместимых сред, Эль-76 для отечественной серии "e;Эльбрус"e;, Том в одноименной интегрированной системе В.Л.Темова и др. К сожалению, системы программирования, поддерживающие разные языки программирования, несовместимы между собой в том смысле, что нельзя написать часть программы на одном языке программирования, часть на другом или воспользоваться программой, написанной на другом языке. Если бы эта проблема "e;модульной совместимости"e; различных языков программирования была решена, то только тогда технические характеристики языков программирования приобрели бы существенный вес при выборе конкретного языка для решения конкретной задачи. Сейчас же определяющим фактором при таком выборе служат не свойства задачи и языка программирования как инструмента её изолированного решения, а тот язык и та среда, с помощью которых обеспечены программные услуги. Которыми необходимо пользоваться при решении задачи. Другими словами, действует принцип инерции программной среды: развивать среду лучше всего её "e;родными"e; средствами. <=== Проф. В.Ш.Кауфман сформулировал четыре заповеди программиста: 1. Выбирай не столько базовый язык программирования, сколько базовую операционную среду (с учетом потребностей всего жизненного цикла создаваемого изделия). 2. На основе выбранного базового языка программирования создавай свой проблемно-ориентированный язык для каждой значимой задачи с учетом выбранной технологии. 3. Язык программирования тем лучше, чем дешевле с его помощью оказывать программные услуги. 4. Минимальное ядро языка программирования плюс проблемно-ориентированные языковые модули - разумный компромисс сундука с чемоданчиком.
Просмотров: 409 | Добавил: AdnrNick | Рейтинг: 0.0/0
Всего комментариев: 0
avatar