Язык программирования

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Язык программи́рования — формальная знаковая система, предназначенная для записи компьютерных программ. Язык программирования определяет набор лексических, синтаксических и семантических правил, определяющих внешний вид программы и действия, которые выполнит исполнитель (обычно — ЭВМ) под её управлением.

Со времени создания первых программируемых машин человечество придумало более восьми тысяч языков программирования (включая эзотерические, визуальные и игрушечные[en])[1]. Каждый год их число увеличивается. Некоторыми языками умеет пользоваться только небольшое число их собственных разработчиков, другие становятся известны миллионам людей. Профессиональные программисты могут владеть десятком и более разных языков программирования.

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

Как правило, язык программирования существует в нескольких, но существенно отличающихся видах:

  • стандарта языка — набора спецификаций, определяющих его синтаксис и семантику; стандарт языка может исторически развиваться>>>
  • воплощений (реализаций) стандарта — собственно программных средств, обеспечивающих работу согласно тому или иному варианту стандарта языка; такие программные средства различаются по производителю, марке и варианту (версии), времени выпуска, полноте воплощения стандарта, дополнительным возможностям; могут иметь определённые ошибки или особенности воплощения, влияющие на практику использования языка или даже на его стандарт.

История[править | править вики-текст]

Ранние этапы развития[править | править вики-текст]

Можно сказать, что первые языки программирования возникали ещё до появления современных электронных вычислительных машин: уже в XIX веке были изобретены устройства, которые можно с долей условности назвать программируемыми — к примеру, механические пианино и ткацкие станки. Для управления ими использовались наборы инструкций, которые в рамках современной классификации можно считать прототипами предметно-ориентированных языков программирования. Значимым можно считать «язык», на котором леди Ада Августа графиня Лавлейс написала программу для вычисления чисел Бернулли для Аналитической машины Чарльза Бэббиджа, ставшей бы, в случае реализации, первым компьютером — хотя и механическим, с паровым двигателем — в мире.

В 19301940 годах, А. Чёрч, А. Тьюринг, А. Марков разработали математические абстракции (лямбда-исчисление, машину Тьюринга, нормальные алгоритмы) соответственно — для формализации алгоритмов.

В это же время, в 1940-е годы, появились электрические цифровые компьютеры и был разработан язык, который можно считать первым высокоуровневым языком программирования для ЭВМ — «Plankalkül», созданный немецким инженером К. Цузе в период с 1943 по 1945 годы[2].

Программисты ЭВМ начала 1950-х годов, в особенности таких, как UNIVAC и IBM 701, при создании программ пользовались непосредственно машинным кодом, запись программы на котором состояла из единиц и нулей и который принято считать языком программирования первого поколения (при этом разные машины разных производителей использовали различные коды, что требовало переписывать программу при переходе на другую ЭВМ).

Первым практически реализованным языком стал в 1949 году так называемый «Краткий код», в котором операции и переменные кодировались двухсимвольными сочетаниями. Он был разработан в компании Eckert–Mauchly Computer Corporation, выпускавшей UNIVAC-и, созданной одним из сотрудников Тьюринга, Джоном Мокли. Мокли поучил своим сотрудникам разработать транслятор математических формул, однако для 1940-х годов эта цель была слишком амбициозна. Краткий код был реализован с помощью интерпретатора[3].

Вскоре на смену такому методу программирования пришло применение языков второго поколения, также ограниченных спецификациями конкретных машин, но более простых для использования человеком за счёт использования мнемоник (символьных обозначений машинных команд) и возможности сопоставления имён адресам в машинной памяти. Они традиционно известны под наименованием языков ассемблера и автокодов. Однако, при использовании ассемблера становился необходимым процесс перевода программы на язык машинных кодов перед её выполнением, для чего были разработаны специальные программы, также получившие название ассемблеров. Сохранялись и проблемы с переносимостью программы с ЭВМ одной архитектуры на другую, и необходимость для программиста при решении задачи мыслить терминами «низкого уровня» — ячейка, адрес, команда. Позднее языки второго поколения были усовершенствованы: в них появилась поддержка макрокоманд.

С середины 1950-х начали появляться языки третьего поколения, такие как Фортран, Лисп и Кобол[4]. Языки программирования этого типа более абстрактны (их ещё называют «языками высокого уровня») и универсальны, не имеют жёсткой зависимости от конкретной аппаратной платформы и используемых на ней машинных команд. Программа на языке высокого уровня может исполняться (по крайней мере, в теории, на практике обычно имеются ряд специфических версий или диалектов реализации языка) на любой ЭВМ, на которой для этого языка имеется транслятор (инструмент, переводящий программу на язык машины, после чего она может быть выполнена процессором).

Обновлённые версии перечисленных языков до сих пор имеют хождение в разработке программного обеспечения, и каждый из них оказал определенное влияние на последующее развитие языков программирования[5]. Тогда же, в конце 1950-х годов, появился Алгол, также послуживший основой для ряда дальнейших разработок в этой сфере. Необходимо заметить, что на формат и применение ранних языков программирования в значительной степени влияли интерфейсные ограничения[6].

Совершенствование[править | править вики-текст]

В период 1960-х1970-х годов были разработаны основные парадигмы языков программирования, используемые в настоящее время, хотя во многих аспектах этот процесс представлял собой лишь улучшение идей и концепций, заложенных ещё в первых языках третьего поколения.

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

Кроме того, в 1960—1970-х годах активно велись споры о необходимости поддержки структурного программирования в тех или иных языках[12]. В частности, голландский специалист Э. Дейкстра выступал в печати с предложениями о полном отказе от использования инструкций GOTO во всех высокоуровневых языках. Развивались также приёмы, направленные на сокращение объёма программ и повышение продуктивности работы программиста и пользователя.

Объединение и развитие[править | править вики-текст]

В 1980-е годы наступил период, который можно условно назвать временем консолидации. Язык С++ объединил в себе черты объектно-ориентированного и системного программирования, правительство США стандартизировало язык Ада, производный от Паскаля и предназначенный для использования в бортовых системах управления военными объектами, в Японии и других странах мира осуществлялись значительные инвестиции в изучение перспектив так называемых языков пятого поколения, которые включали бы в себя конструкции логического программирования[13]. Сообщество функциональных языков приняло в качестве стандарта ML и Лисп. В целом этот период характеризовался скорее опорой на заложенный в предыдущем десятилетии фундамент, нежели разработкой новых парадигм.

Важной тенденцией, которая наблюдалась в разработке языков программирования для крупномасштабных систем, было сосредоточение на применении модулей — объёмных единиц организации кода. Хотя некоторые языки, такие, как ПЛ/1, уже поддерживали соответствующую функциональность, модульная система нашла своё отражение и применение также и в языках Модула-2, Оберон, Ада и ML. Часто модульные системы объединялись с конструкциями обобщённого программирования[14].

Важным направлением работ становятся визуальные (графические) языки программирования, в которых процесс «написания» программы как текста заменяется на процесс «рисования» (конструирования программы в виде диаграммы) на экране ЭВМ. Визуальные языки обеспечивают наглядность и лучшее восприятие логики программы человеком.

В 1990-х годах в связи с активным развитием Интернета распространение получили языки, позволяющие создавать сценарии для веб-страниц — главным образом Perl, развившийся из скриптового инструмента для Unix-систем, и Java. Возрастала также и популярность технологий виртуализации. Эти изменения, однако, также не представляли собой фундаментальных новаций, являясь скорее совершенствованием уже существовавших парадигм и языков (в последнем случае — главным образом семейства Си).

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

Спецификация языков[править | править вики-текст]

Стандартизация[править | править вики-текст]

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

Алфавит[править | править вики-текст]

Современные языки программирования рассчитаны на использование ASCII, то есть доступность всех графических символов ASCII является необходимым и достаточным условием для записи любых конструкций языка. Управляющие символы ASCII используются ограниченно: допускаются только возврат каретки CR, перевод строки LF и горизонтальная табуляция HT (иногда также вертикальная табуляция VT и переход к следующей странице FF).

Ранние языки, возникшие в эпоху 6-битных символов, использовали более ограниченный набор. Например, алфавит Фортрана включает 49 символов (включая пробел): A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 = + - * / () . , $ ' :

Заметным исключением является язык APL, в котором используется очень много специальных символов.

Использование символов за пределами ASCII (например, символов KOI8-R или символов Юникода) зависит от реализации: иногда они разрешаются только в комментариях и символьных/строковых константах, а иногда и в идентификаторах. В СССР существовали языки, где все ключевые слова писались русскими буквами, но большой популярности>>> подобные языки не завоевали (исключение составляет Встроенный язык программирования 1С:Предприятие).

Расширение набора используемых символов сдерживается тем, что многие проекты по разработке программного обеспечения являются международными. Очень сложно было бы работать с кодом, где имена одних переменных записаны русскими буквами, других — арабскими, а третьих — китайскими иероглифами. Вместе с тем, для работы с текстовыми данными языки программирования нового поколения (Delphi 2006, C#, Java) поддерживают Unicode.

Грамматика[править | править вики-текст]

Семантика[править | править вики-текст]

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

  • При описании семантики в рамках операционного подхода обычно исполнение конструкций языка программирования интерпретируется с помощью некоторой воображаемой (абстрактной) ЭВМ.
  • Аксиоматическая семантика описывает последствия выполнения конструкций языка с помощью языка логики и задания пред- и пост-условий.
  • Денотационная семантика оперирует понятиями, типичными для математики — множества, соответствия, а также суждения, утверждения и др.

Классификация[править | править вики-текст]

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

Конкретный язык программирования в подавляющем большинстве случаев имеет более одного языка-предка. Многие языки создаются как сочетание элементов различных языков. В одних случаях такое сочетание проходит тщательный математический анализ на предмет непротиворечивости (см., например, Определение Standard ML), в других — язык формируется как бессистемная сумма элементов, порой взаимно-противоречивых (как в случае C++[15]).

Языки низкого и высокого уровня[править | править вики-текст]

Формально язык программирования не имеет такого критерия как «уровень». Условно этот термин означает степень отличия семантики языка от машинного кода целевой архитектуры процессора — другими словами, наименьший масштаб преобразований, которые должен претерпеть код программы перед тем, как он сможет исполняться. Однако, история знает случаи, когда язык, являющийся «низкоуровневым» на одной платформе, компилировался как «высокоуровневый» на другой (таким образом программы на ассемблере VAX использовались на DEC Alpha), или когда язык, считающийся «высокоуровневым», реализовывался аппаратно (см. Лисп-машина, Java Optimized Processor[en]).

К языкам самого низкого уровня относят машинные языки. Их относят к первому поколению языков программирования[en]. Вскоре после них появились языки второго поколения[en] — так называемые «ассемблеры». В одних случаях они представляют собой не более чем мнемонику над машинным языком, в других имеют весьма развитый макроязык. Языки первого и второго поколения позволяют тонко контролировать, как требуемая функциональность будет исполняться на данном процессоре с учётом особенностей его архитектуры. С одной стороны, это обеспечивает высокое быстродействие и компактность программ, но с другой, для переноса программы на другую аппаратную платформу её нужно перекодировать (а порой и перепроектировать) с нуля. Большинство ассемблеров являются бестиповыми, но существуют и типизированные ассемблеры[en], нацеленные на обеспечение минимальной безопасности>>> низкоуровневых программ.

К 1970-м годам сложность программ выросла настолько, что превысила способность программистов управляться с ними, и это привело к огромным убыткам и застою в развитии информационных технологий[16]. Ответом на эту проблему стало появление массы языков высокого уровня, предлагающих самые разные способы управления сложностью (подробнее см. парадигма программирования и языки для программирования в мелком и крупном масштабе). Термин «язык высокого уровня» в этом смысле предполагает, что семантическая модель языка в большей степени учитывает особенности мышления человека, нежели машины. Как следствие, программы гораздо легче модифицируются и совсем легко переносятся с компьютера на компьютер. На практике, наибольшее распространение получили языки третьего поколения, которые лишь претендуют на звание «высокоуровневых», но реально предоставляют лишь те «высокоуровневые» конструкции, что находят однозначное соответствие инструкциям в машине фон-Неймана[17].

Языки четвёртого и пятого поколения нацелены преимущественно на выражение того, что должна делать программа (см. декларативное программирование). На таких языках в большинстве случаев достаточно просто перекомпилировать программу под определенную компьютерную архитектурную и операционную систему. Кроме того, для них не редкость компиляция в язык третьего поколения (в большинстве случаев это Си, но иногда в роли целевых платформ выступают Java, JavaScript, Ada и др.).

Язык Си изначально позиционировался как «высокоуровневый ассемблер»; его также часто называют «языком среднего уровня». Он позволяет в значительной степени контролировать способ реализации алгоритма с учётом свойств, типичных для весьма большого числа аппаратных архитектур. Однако, есть платформы, под которые реализации Си (даже с в нестандартном виде) отсутствуют по причине принципиальной невозможности или нецелесообразности их создания. Со временем появились и другие языки среднего уровня, например, LLVM, C--.

В большинстве случаев языки высокого уровня порождают машинный код большего размера и исполняются медленнее. Однако, некоторые языки высокого уровня для алгоритмически и структурно сложных программ могут давать заметное преимущество в эффективности, уступая низкоуровневым лишь на небольших и простых программах (подробнее см. эффективность языков). Иначе говоря, потенциальная эффективность языка меняется с повышением его «уровня» нелинейно и вообще неоднозначно. Однако, скорость разработки и трудоёмкость модификации, устойчивость и другие показатели качества в сложных системах оказываются гораздо важнее предельно возможной скорости исполнения — они обеспечивают различие между программой, что работает, и той, что нет[18] — так что экономически более целесообразна эволюция аппаратного обеспечения (исполнение большего числа инструкций в единицу времени) и методов оптимизирующей компиляции (более того, поледние десятилетия эволюция аппаратного обеспечения движется в направлении поддержки методов оптимизирующей компиляции для языков высокого уровня).

Поэтому в наши дни языки низкого уровня используются только в задачах системного программирования. Как следствие, сам термин «низкоуровневый язык программирования» многими людьми понимается исключительно как «предоставляющий возможность решать задачи системного программирования». В действительности, этому определению отвечают и многие языки, требующие значительных преобразований до получения машинного кода. Более того, языки C# и Limbo используются именно в роли системных в операционных системах с повышенной отказоустойчивостью. Язык BitC является представителем четвёртого поколения (функциональной парадигмы программирования), но целиком и полностью ориентирован именно на системное программирование, и уверенно конкурирует по скорости с Си. Таким образом, его классификация как языка «низкого» или «высокого» уровня оказывается весьма субъективной.

Безопасные и небезопасные языки[править | править вики-текст]

Современные компьютеры представляют сложные данные реального мира в двоичном коде (хотя существуют и компьютеры, работающие в других системах счисления — см. троичный компьютер). Это вводит в дисциплину программирования риск человеческого фактора — вероятность ошибок доступа к памяти. Поэтому многие языки программирования сопровождаются средством контроля смысла операций над двоичными данными на основе сопровождающей их логической информации — системой типов. Однако, существуют и бестиповые языки, например, Forth.

Системы типов языков делятся на динамические (потомки Lisp, Smalltalk, APL) и статические, а последние, в свою очередь, делятся на неполиморфные (потомки Алгола и BCPL) и полиморфные (потомки ML)[19]. Кроме того, они делятся на явные (англ. explicit) и неявные (англ. implicit) — другими словами, требующие манифестной[en] декларации типов для объектов в программе или статически выводящие их самостоятельно.

Системы типов бывают сильные и слабые. Сильная система типов назначает тип для всякого выражения раз и навсегда (когда бы конкретно это ни происходило — в динамике или в статике), а слабая позволяет в последствии переназначать типы. Сильная типизация порой ошибочно отождествляется со статической.

В общем и целом, язык называется безопасным, если программы на нём, которые могут быть приняты компилятором как правильно построенные, в динамике никогда не выйдут за рамки допустимого поведения[20]. Это не значит, что такие программы не содержат ошибок вообще. Термин «хорошее поведение программы» (англ. well behavior) означает, что даже если программа содержит некий баг (в частности, логическую ошибку), она тем не менее не способна нарушить целостность данных и обрушиться[en] (англ. go wrong). Хотя термины неформальны, безопасность некоторых языков (например, Standard ML) математически доказуема[19]. Безопасность других (например, Ada) была обеспечена ad hoc-образом, без обеспечения концептуальной целостности, что может обернуться катастрофами, если положиться на них в ответственных задачах (см. концептуальная целостность языков). Неформальная терминология была популяризована Робином Милнером, одним из авторов теории формальной верификации и собственно языка Standard ML.

Степень контроля ошибок и реакция языка на них могут различаться. Простейшие системы типов запрещают, к примеру, вычитать строку из целого числа. Однако, целыми числами могут представляться и миллиметры, и дюймы, но было бы логической ошибкой вычитать дюймы из миллиметров. Развитые системы типов позволяют (а наиболее развитые — принуждают) внедрять в программу такую логическую информацию. Для ЭВМ она является избыточной, и полностью удаляется при порождении машинного кода тем или иным образом>>>. В частности, Standard ML не допускает над данными никаких операций, кроме тех, что разрешены явно и формализованы; однако, программы на нём всё же могут завершаться порождением необработанного исключения (например, при попытке деления на ноль). Его потомок, MLPolyR гарантирует также и отсутствие необработанных исключений. Такие языки называются «типобезопасными». Java и C# менее строги и контролируют лишь утечки памяти, поэтому в их контексте чаще используют более узкий термин «безопасность типов в отношении доступа к памяти» (англ. memory type safety) или (чаще) просто «безопасность доступа к памяти». Сильно динамически типизируемые языки отслеживают поведение программ в динамике (что влечёт снижение быстродействия) и реагируют на ошибки порождением исключения. Все эти языки ориентированы на практичность, предоставляя оптимальный компромисс между пресечением серьёзных сбоев и высокой скоростью разработки программ. Но существуют и языки, предназначенные для написания программ, которые верны по построению, то есть обеспечивающие гарантию того, что исполнимая программа по структуре и поведению будет тождественна её спецификации (см. параметричность[en], зависимый тип). Как следствие, программы на таких языках часто называют «исполнимыми спецификациями» (см. Соответствие Карри — Говарда). Трудоёмкость разработки на таких языках возрастает на порядки, кроме того, они требуют очень высокой квалификации разработчика, поэтому они используются только в формальной верификации. Примерами таких языков служат Agda, Coq.

Языки Си и его потомок C++ являются небезопасными[21]. В программах на них обширно встречаются ситуации ослабления типизации (приведение типов) и прямого её нарушения (каламбур типизации), так что ошибки доступа к памяти являются в них статистической нормой (но крах[en] программы наступает далеко не сразу, что затрудняет поиск места ошибки в коде). Самые мощные системы статического анализа для них способны обнаруживать не более 70 — 80 % ошибок (см. PVS-Studio[en],[22][23]), но их использование обходится очень дорого в денежном смысле. Достоверно же гарантировать безотказность программ на этих языках невозможно, не прибегая к формальной верификации, что не только ещё дороже, но и требует специальных знаний (см. примеры (англ.)). У Си есть и безопасные потомки, такие как Cyclone или Rust.

Язык Forth не претендует на звание «безопасного», но, тем не менее, на практике существование программ, способных повредить данные, почти исключено, так как содержащая потенциально опасную ошибку программа аварийно завершается на первом же тестовом запуске, принуждая к коррекции исходного кода. В сообществе Erlang принят подход «let it crash» (рус. дай ей обрушиться), также нацеленный на раннее выявление ошибок.

Компилируемые и интерпретируемые языки[править | править вики-текст]

Можно выделить три принципиально разных способа реализации языков программирования: компиляция, интерпретация и встраивание. Распространено заблуждение, согласно которому способ реализации является присущим конкретному языку свойством. В действительности, это деление до определенной степени условно. В ряде случаев язык имеет формальную семантику, ориентированную на интерпретацию, но все или почти все его действительные реализации являются компиляторами, порой весьма эффективно оптимизирующими (примерами могут служить языки семейства ML, такие как Standard ML, Haskell). Есть языки, размывающие границы между интерпретацией и компиляцией — например, Forth.

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

Естественный для языка способ реализации определяется временем связывания программных элементов с их характеристиками. В частности, в языках со статической типизацией переменные и другие объекты программы связываются с типом данных на этапе компиляции, а в случае типизации динамической — на этапе выполнения, как правило — в произвольной точке программы. Некоторые свойства элементов языка, такие как значение арифметических операторов или управляющих ключевых слов, могут быть связаны уже на этапе определения языка. В других языках возможно их переназначение (см. связывание имён[en]). Раннее связывание обычно означает бо́льшую эффективность программы, в то время как позднее — большую гибкость, ценой которого является меньшая скорость и/или усложнение соответствующего этапа[24]. Однако, даже из казалось бы очевидных случаев есть исключения — например, интенсиональный полиморфизм откладывает обработку статической типизации до этапа выполнения, но не замедляя, а повышая общее быстродействие (по крайней мере, в теории).

Для любого традиционно компилируемого языка (такого как Паскаль) можно написать интерпретатор. Но многие интерпретируемые языки предоставляют некоторые дополнительные возможности, такие как динамическая генерация кода (см. eval[en]), так что их компиляция должна быть динамической (см. динамическая компиляция). Таким образом, составной термин «язык + способ его реализации» в ряде случаев оказывается уместен. Кроме того, большинство современных «чистых» интерпретаторов не исполняют конструкции языка непосредственно, а компилируют их в некоторое высокоуровневое промежуточное представление (например, с разыменованием переменных и раскрытием макрокоманд). Большинство традиционно интерпретируемых или компилируемых языков могут реализовываться как встраиваемые, хотя метаязыков, которые были бы способны охватить другие языки как своё подмножество, не так много (наиболее ярким представителем является Lisp).

Как правило, скомпилированные программы выполняются быстрее и не требуют для выполнения дополнительных программ, так как уже переведены на машинный язык. Вместе с тем, при каждом изменении текста программы требуется её перекомпиляция, что замедляет процесс разработки. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция. Интерпретируемые языки позволяют запускать программы сразу же после изменения, причём на разных типах машин и операционных систем без дополнительных усилий, а гомоиконные — и вовсе динамически перемещать программу между разными машинами без прерывания её работы (наиболее общий случай сериализации), позволяя разрабатывать системы непрерывной доступности[en] (см.тж. системы высокой доступности). Портируемость интерпретируемой программы определяется только наличием реализаций интерпретаторов под те или иные аппаратные платформы. Ценой всего этого становятся заметные потери быстродействия; кроме того, если программа содержит фатальную ошибку, то об этом не будет известно, пока интерпретатор не дойдёт до её места в коде (в отличие от статически типобезопасных языков>>>).

Некоторые языки, например, Java и C#, находятся между компилируемыми и интерпретируемыми. А именно, программа компилируется не в машинный язык, а в машинно-независимый код низкого уровня, байт-код. Далее байт-код выполняется виртуальной машиной. Для выполнения байт-кода обычно используется интерпретация, хотя отдельные его части для ускорения работы программы могут быть транслированы в машинный код непосредственно во время выполнения программы по технологии компиляции «на лету» (Just-in-time compilation, JIT). Для Java байт-код исполняется виртуальной машиной Java (Java Virtual Machine, JVM), для C# — Common Language Runtime. Подобный подход в некотором смысле позволяет использовать плюсы как интерпретаторов, так и компиляторов.

Языки первого и высшего порядка[править | править вики-текст]

Начальные сведения[править | править вики-текст]

Математическая логика классифицируется по порядку — см. логика первого порядка и логика высшего порядка. Эта терминология естественным образом наследуется информатикой, образуя семантики, соответственно, первого и высшего порядка[25]. Языки первого порядка (например, потомки Алгола, такие как Basic или классический Pascal Вирта) позволяют определять только зависимости первого порядка между величинами. Например, значение sqare x зависит от значения x. Такие зависимости называются функциями. Языки высшего порядка (в первую очередь это все функциональные языки) позволяют определять зависимости между зависимостями. Например, значение map f x зависит от значений f и x, где значение f само выражает абстрактную зависимость (другими словами, параметр f варьируется над множеством функций определённой сигнатуры). Такие зависимости называются функциями высшего порядка. При этом в большинстве случаев говорят, что такой язык рассматривает зависимости (функции) как объекты первого класса, иначе говоря, допускает функции первого класса[26] (некоторые языки, например Си, не поддерживают первоклассные функции, но предоставляют ограниченные возможности строить функции высшего порядка). Эти термины ввёл Кристофер Стрэчи[en]. С развитием систем типов различение порядков распространилось и на типы (см. конструктор типов).

Выразительность[править | править вики-текст]

Языки первого порядка позволяют воплощать в виде кода алгоритмы, но не архитектуру программ. По мнению Стрэчи[en], это ограничение унаследовано языком Алгол (а от него другими языками) из классической математики, где используются только константные операции и функции, однозначно распознаваемые вне контекста, и отсутствует систематичная нотация для произвольной работы с функциями (в качестве такой нотации в 1930-х годах было построено лямбда-исчисление, которое позже легло в основу языков высшего порядка)[27]. Схемы взаимодействия компонентов (процедур, функций, объектов, процессов и др.) для программ на языках первого порядка могут существовать лишь на условном уровне, вне самих программ. Со временем были обнаружены многократно повторяющиеся однотипные схемы такого рода, в результате чего вокруг них выстроилась самостоятельная методология — шаблоны проектирования. Языки высшего порядка позволяют воплощать такие схемы в виде исполнимого кода, пригодного для многократного использования (функций, предназначенных для преобразования и композиции других функций — см., например, конверторы и сканеры в SML)[28][29]. В результате, решения, которые на языках первого порядка могут быть представлены фрагментами программ (порой довольно сложными и громоздкими), на языках высшего порядка могут сокращаться до одной команды или вообще использования элемента семантики самого языка, не имеющего синтаксического выражения. Например, шаблон «Команда», часто применяемый в языках первого порядка, эквивалентен непосредственно самому понятию функции первого класса. То же распространяется и на более высокие слои языков — типизацию (см. полиморфизм в высших рода́х) и типизацию типизации (см. полиморфизм родо́в).

Преимущественно, сказанное относится к языкам, семантика которых основана на лямбда-исчислении (потомки Lisp, ML). Однако, некоторые языки иной природы также предоставляют возможность программирования высшего порядка[en]. Примерами служат стековые языки (Forth) и определённая разновидность объектно-ориентированных языков (Smalltalk, CLOS, см. сообщение высшего порядка[en]).

Изучение[править | править вики-текст]

Введя терминологию «сущностей первого и второго класса», Стрэчи[en] тут же акцентировал внимание на том, что из личного опыта и обсуждений со множеством людей он убедился, что невероятно тяжело перестать думать о функциях как об объектах второго класса[27]. То есть порядок языка имеет ярко выраженное психологическое влияние (см. гипотеза Сепира — Уорфа). Есть и более жёсткие высказывания, например:

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

Эдсгер Дейкстра

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

Парадигма программирования[править | править вики-текст]

Парадигмы программирования
 • Императивная
(контрастирует с декларативной)
Процедурная
Структурная
Аспектно-ориентированная
Объектно-ориентированная
Агентно-ориентированная
Компонентно-ориентированная
Прототипно-ориентированная
Обобщённое программирование

 • Декларативная
(контрастирует с императивной)

Чистота языка
Чистота функции
Функциональная
В терминах Рефал-машины
Аппликативная
Комбинаторная
Бесточечная
(чистая конкатенативная)
Логическая
Ограничениями

 • Конкатенативная
 • Векторная[en]
 • Метапрограммирование

Языково-ориентированная
Предметно-ориентированная
Пользователями[en]
Автоматизация процесса программирования
Рефлексивность
Гомоиконность

 • Связанные темы

Программирование в крупном и мелком масштабе[en]
Модульность
Полиморфизм
Продолжения и CPS
Параллелизм и конкурентность

 • Методы и алгоритмы

Автоматное
Динамическое
Потоков данных
Событийно-ориентированное
Реактивное
Сервис-ориентированное

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

Технически языки делятся, например, на допускающие побочные эффекты и ссылочно-прозрачные. Во втором случае говорят, что язык принадлежит к «чисто функциональной парадигме». В качестве парадигмы также иногда рассматриваются определённые свойства системы типов и стратегии вычисления языка, например, для параметрически полиморфных систем типов нередко говорят о реализации парадигмы обобщённого программирования. Другим примером может служить свойство гомоиконности, открывающее целый спектр разновидностей метапрограммирования. Существует масса «языков, наследованных от математики»>>>, многие из которых формируют уникальные парадигмы. Яркими представителями являются Lisp, впервые воплотивший лямбда-исчисление и положивший таким образом начало функциональной парадигме; Smalltalk, впервые воплотивший объектно-ориентированную парадигму (появившаяся за много лет до него Симула поддерживала понятие класса, но воплощала структурную парадигму) и стековый язык Forth, воплощающий конкатенативную парадигму.

Более условно, языки делятся на пять поколений, из которых три первых являются представителями императивной парадигмы программирования, а два последних — декларативной[30]. Термин «императив» означает «приказной порядок», то есть программирование посредством пошагового инструктирования машины, или детального указания уже придуманного программистом способа реализации технического задания. Термин «декларатив» означает «описание», то есть программирование посредством предоставления формализации технического задания в виде, пригодном для автоматических преобразований[en], с предоставлением свободы выбора транслятору языка.

Языки первого[en] и второго[en] поколения являются низкоуровневыми, т.е. ориентированными на специфику конкретного аппаратного обеспечения, и в принципе не соотносятся с какой-либо парадигмой (хотя конкретный разработчик на них, разумеется, может идеологически следовать определённым тенденциям). Они позволяют описывать лишь, как получить результат, тогда как языки четвёртого и пятого поколения претендуют на возможность описывать, что требуется в результате. Поэтому первые называют как-языками (или языками, ориентированными на машину), а вторые — что-языками (или языками, ориентированными на человека). Языки третьего поколения находятся по середине и в зависимости от степени пригодности к задаче могут рассматриваться как те или другие. Для множества задач полностью автоматическое порождение по-настоящему эффективной реализации алгоритмически неразрешимо, так что на практике даже на что-языках нередко используются определённые алгоритмические ухищрения. Однако, существуют методы получения эффективных реализаций из основанных на определении (реализаций «в лоб») — такие как изобретённая в СССР суперкомпиляция.

С ростом размера и сложности программ уже при использовании языков второго поколения начала формироваться парадигма процедурного программирования, требующая производить декомпозицию крупных процедур в цепочку иерархически связанных более мелких. Примерно в то же время появились первые языки третьего поколения[en] и сформировалось сперва структурное программирование как прямое развитие процедурного, а затем и модульное. Со временем появилось огромное количество разнообразных путей решения проблемы комплексирования растущих программных систем>>> с сохранением исходно императивного подхода в основе. В некоторых случаях достигнуто существенное влияние на показатели скорости разработки и качества, но в общем и целом, как выше отмечено>>>, языки третьего поколения абстрагируются от машинной логики лишь до определённого уровня и незначительно подвержены эквивалентным преобразованиям[en]. К настоящему времени третье поколение языков представлено наиболее обширным спектром разнообразных парадигм.

К четвёртому поколению[en] относят функциональные языки, из которых выделяются «чисто функциональные» (англ. purely functional, соответствующие выше упомянутой технической категории ссылочно-прозрачных), а остальные называются «не чисто функциональными» (англ. impurely functional)

К пятому поколению[en] относят языки логического программирования, в котором, помимо традиционного, выделяется несколько особых форм, например, программирование ограничениями. Фактически, языки пятого поколения — это языки четвёртого поколения, дополненные базой знаний[30].

Многие парадигмы являются условно провозглашёнными методиками организации структуры программы и применимы к большому множеству языков. Наиболее широкий охват имеют структурная и модульная — они применяются и в императивных, и в декларативных языках. Другие парадигмы тесно связаны с техническими свойствами. Например, подмножество языка С++ — шаблоны — формально может рассматриваться как полный по Тьюрингу чисто функциональный язык, но С++ не обладает присущими функциональным языкам свойствами (ссылочная прозрачность, типобезопасность, гарантия оптимизации хвостовых вызовов и др.). Как следствие, применяемые в компиляции функциональных языков алгоритмы не могут быть применены к С++, и потому ведущие исследователи функциональной парадигмы отзываются о С++ весьма скептически (подробнее см. критика шаблонов С++).

Языки для программирования в мелком и крупном масштабе[править | править вики-текст]

Программы могут решать задачи различного масштаба[en]: одна программа строит график для заданной функции, а другая управляет документооборотом крупного предприятия. Различные языки программирования рассчитаны на разный исходный масштаб задачи и, что ещё более важно, по-разному справляются с ростом сложности программных систем. Ключевым качеством языка, от которого зависит, как меняется трудоёмкость разработки по мере наращивания системы, является абстракция, т.е. возможность отделять смысл (поведение) компонента системы от способа его реализации[31][32].

Рост сложности любой программной системы принципиально ограничен тем пределом, до которого ещё можно сохранять контроль над ней: если объём информации, требуемый для осмысления компонента этой системы, превышает «вместимость» мозга одного человека, то этот компонент не будет до конца понят. Станет чрезвычайно тяжело дорабатывать его или исправлять ошибки, и от каждой корректировки можно ждать введения новых ошибок из-за этого неполного знания.

— Martin Ward, «Language Oriented Programming»[33]

Такие показатели качества исходного кода, как тестируемость и модифицируемость, очевидным образом определяются коэффициентом повторного использования. Это может означать как применение разных функций к одному и тому же компоненту, так и возможность применять одну и ту же функцию к разным компонентам. Параметрически полиморфные (особенно выводящие) и динамические системы типов существенно повышают коэффициент повторного использования: например, функция, вычисляющая длину массива, будет применима к бесконечному множеству типов массивов[19][34]. Если же язык требует в сигнатуре функции указывать конкретный способ реализации входных данных, то этот коэффициент резко страдает. Например, Pascal критиковался за необходимость всегда указывать конкретный размер массива[35], а C++ — за необходимость различать . и -> при обращении к компонентам составных данных[en][36]. Языки высшего порядка>>> позволяют выделять схемы взаимодействия функций в многократно вызываемый блок кода (функцию высшего порядка)[28][37], а наибольших значений повторное использование достигает при переходе к языку более высокого уровня — при необходимости специально разрабатываемого для данной задачи — в этом случае повторно используется язык, а не одна функция[33], а сама разработка языка может вестись с интенсивным повторным использованием компонентов компилятора[38].

С развитием языков появились особые (присущие исключительно программированию, не требовавшиеся ранее в математике) категории компонентов и зависимостей: монады, классы типов, полиморфные ветвления, аспекты и др. Их использование позволяет выражать бо́льшую функциональность в том же объёме кода, тем самым переводя программирование-по-крупному[en] в более мелкий масштаб.

Другие фундаментальные проблемы, связанные со сложностью крупных систем, лежат вне самих программ: это взаимодействие разрабатывающих её программистов между собой, документирование и т.д. Помимо обеспечения абстракции, не последнюю роль в этом играет концептуальная целостность>>> выбранного языка программирования.[39][33]

Кроме свойств семантики языка, повторное использование может обеспечиваться посредством модульной структуры программной системы или комплекса. Более того, сколь бы гибким ни был язык, работа с огромными объёмами кодов, особенно множеством людей, требует их декомпозиции на модули тем или иным образом. Модульная структура подразумевает не просто разбиение монолитного исходного кода программы на множество текстовых файлов, а обеспечение абстракции в более крупном масштабе, т.е. определение интерфейса для всякого логически завершённого фрагмента и сокрытие деталей его реализации. В зависимости от применённых в языке правил определения области видимости язык может допускать или не допускать автоматическое определение зависимостей. Если согласно правилам возможен конфликт имён, то автоопределение зависимостей невозможно, и тогда в заголовке модуля требуется явно перечислять имена модулей, компоненты которых в нём используются.

Некоторые языки (например, Basic или классический Pascal Вирта) ориентированы исключительно на разработку мелких, структурно простых программ. Они не обеспечивают ни развитой системы модулей, ни гибкости конкретных фрагментов. Язык Си создавался как «высокоуровневый ассемблер», что само по себе не предполагает разработку систем выше некоторого порога сложности, поэтому поддержка крупномасштабного программирования в него заложена также не была. Некоторые языки высокого и сверх-высокого уровня (Erlang, Smalltalk, Prolog) предоставляют в качестве базовых примитивных элементов концепции, которые в других языках представляются конструктивно и алгоритмически сложными (процессы, классы, базы знаний) — аналогично разнообразным математическим исчислениям (см. также концептуальная целостность языков). Поэтому такие языки нередко рассматриваются в роли предметно-специфичных — на них выглядит простыми некоторые (но далеко не все) задачи, которые на других языках выглядят сложными. Однако, расширение функциональности в других аспектах на этих языках может оборачиваться затруднениями. Standard ML и его родственники расслаиваются на два языка, из которых один — «язык-ядро» (англ. core language) — ориентирован на разработку простых программ, а другой — «язык модулей» (англ. module language),— соответственно, на нелинейную компоновку их в сложные программные системы. Со временем были построены варианты слияния их воедино (1ML). Многие другие языки также включают системы модулей, но большинство из них являются языками модулей первого порядка>>>. Язык модулей ML является единственным в своём роде языком модулей высшего порядка>>>. Языки Lisp и Forth позволяют наращивать системы произвольно и безгранично, в том числе позволяя создавать встраиваемые предметно-специфичные языки внутри себя (как своё синтаксическое и семантическое подмножество) — поэтому их нередко называют метаязыками.

Наиболее популярным на сегодняшний день подходом к решению проблемы комплексирования является объектно-ориентированное программирование, хотя успешность его применения на протяжении десятилетий существования неоднократно подвергалась скепсису, и до сих пор отсутствуют достоверные данные о том, что он приносит выигрыш по сравнению с другими подходами по тем или иным показателям качества. Ему сопутствуют (а порой конкурируют) различные технологии регламентирования зависимостей между компонентами: контракты, прототипы, примеси, типажи и др.

Более мощным подходом исторически считалось использование различных форм метапрограммирования, то есть автоматизации самого процесса разработки на различных уровнях. Принципиально различается метапрограммирование внешнее по отношению к языку и доступное в самом языке. При использовании языков первого порядка>>> сложность растущих программных систем быстро переходит порог способностей человека по восприятию и переработке информации, поэтому применяются внешние средства предварительного визуального проектирования, позволяющие обозревать сложные схемы в упрощённом виде и в уменьшенном масштабе и затем автоматически порождать каркас кода — см. CASE. В сообществах разработчиков, использующих языки высшего порядка>>>, доминирует прямо противоположный подход — пресекать саму возможность выхода сложности из-под контроля за счёт разделения информационных моделей на независимые составляющие и разработки средств автоматического преобразования одних моделей в другие — см. языково-ориентированное программирование.

Концептуальная целостность языков[править | править вики-текст]

Фредерик Брукс[40] и Ч.Э.Р. Хоар[41] делают акцент на необходимости обеспечения концептуальной целостности информационных систем вообще и языков программирования в частности, чтобы в каждой части системы использовались сходные синтаксические и семантические формы и не требовалось осваивать помимо собственно состава системы также и правила её идиоматического использования. В частности, Хоар предсказывал, что сложность Ады станет причиной катастроф, и его предсказание сбылось в 1996 годукосмическая ракета Ариан-5 взорвалась на 37-й секунде после старта. Алан Кэй отделяет языки, являющиеся «стилем во плоти» (англ. crystalization of style) от прочих языков, являющихся «склеиванием возможностей» (англ. agglutination of features)[42]. Грег Нельсон[43] и Эндрю Аппель[en][19] выделяют в особую категорию «языки, наследованные от математики» (англ. mathematically-derived languages).

Эти акценты призывают к использованию языков, воплощающих некое математическое исчисление, аккуратно адаптированное для того, чтобы быть более практичным языком для разработки реальных программ. Такие языки отличаются ортогональностью  (англ.), и хотя это означает необходимость вручную реализовывать многие распространённые идиомы, доступные в более популярных языках в качестве примитивов языка, выразительность[en] таких языков в целом может быть существенно выше.

Лишь некоторые языки попадают под эту категорию; большинство же языков проектируются приоритетно исходя из возможности эффективной трансляции в машину Тьюринга. Многие языки опираются на общие теории, но при разработке они почти никогда не проверяются на безопасность совместного использования конкретных языковых элементов, являющихся частными приложениями этих теорий, что неизбежно приводит к несовместимости между реализациями языка. Эти проблемы либо игнорируются, либо начинают преподноситься как естественное явление (англ. «not a bug, but a feature»), но в действительности их причиной является то, что язык не был подвергнут математическому анализу.[44]

Примеры математически обоснованных языков и воплощаемых ими математических моделей:

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

  • Существенное повышение стабильности программ. В одних случаях — за счёт построения доказательства надёжности для самого языка (см. типобезопасность), существенного упрощения формальной верификации программ, и даже получения языка, который сам является системой автоматического доказательства (Coq, Agda). В других случаях — за счёт раннего обнаружения ошибок на первых же пробных запусках программ (Forth и регулярные выражения).
  • Обеспечение потенциально более высокой эффективности программ. Даже если семантика языка далека от архитектуры целевой платформы компиляции, к нему могут быть применимы формальные методики глобального анализа программ (хотя трудоёмкость написания даже тривиального транслятора может оказаться выше). Например, для языков Scheme и Standard ML существуют развитые полнопрограммно-оптимизирующие компиляторы и суперкомпиляторы, результат работы которых может уверенно конкурировать по скорости с языком низкого уровня Си и даже опережать последний (хотя ресурсоёмкость работы самих компиляторов оказывается значительно выше). Одна из самых быстрых СУБД — KDB[47] — написана на языке K. Язык Scala (унаследовавший математику от ML) обеспечивает на платформе JVM более высокую скорость, чем «родной» для неё язык Java. С другой стороны, Forth имеет репутацию одного из самых нетребовательных к ресурсам языков (менее требователен, чем Си) и используется для разработки приложений реального времени под самые маломощные ЭВМ; кроме того, транслятор Форта является одним из наименее трудоёмких в реализации на ассемблере.
  • Заранее известный (неограниченный или, наоборот, чётко очерченный) предел роста сложности программных компонентов, систем и комплексов, которые можно выразить средствами этого языка с сохранением показателей качества[19][48]. Языки, не имеющие математического обоснования (а именно такие наиболее часто применяются в мейнстриме: C++, Java, C#, Delphi и др.), на практике ограничивают реализуемую функциональность и/или снижают качество по мере усложнения системы[49], так как им присущи экспоненциальные кривые роста сложности как касательно работы одного отдельно взятого человека, так и касательно сложности управления проектом в целом[39][50]. Прогнозируемая сложность системы приводит либо к поэтапной декомпозиции проекта на множество более мелких задач, каждая из которых решается соответствующим языком; либо к языково-ориентированному программированию для случая, когда адресуемой языком задачей является как раз описание семантик и/или символьные вычисления (Lisp, ML, Haskell, Рефал, Регулярные выражения). Языки с неограниченным пределом роста сложности программ нередко относят к метаязыкам (что в непосредственном толковании термина не верно, но практике сводимо, так как всякий мини-язык, выбранный для решения некоторой подзадачи в составе общей задачи, может быть представлен в виде синтаксического и семантического подмножества данного языка, не требуя трансляции[51]).
  • Удобство для человека при решении задач, на которые этот язык ориентирован по своей природе (см. проблемно-ориентированный язык), что в некоторой степени также способно (косвенно) повлиять на повышение стабильности результирующих программ за счёт повышения вероятности обнаружения ошибок в исходном коде и снижения дублирования кода.

Особые категории языков[править | править вики-текст]

Эффективность языков[править | править вики-текст]

Как отмечают многие исследователи (например, В.Ф. Турчин[52]), достоинства всякого формализованного языка определяются не только тем, сколь он удобен для непосредственного использования человеком, но и тем, в какой степени тексты на этом языке поддаются формальным преобразованиям.

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

Для небольших и простых программ языки высокого уровня порождают машинный код большего размера и исполняются медленнее. Однако, для алгоритмически и структурно сложных программ преимущество может быть на стороне некоторых языков высокого уровня, так как человек физически не способен выражать сложные концепции с учётом их эффективного исполнения на языке машины. К примеру, существует бенчмарк, на котором MLton и Stalin Scheme[en] уверенно опережают GCC. Есть масса частных причин, по которым автоматическая оптимизация в ходе трансляции языков высокого уровня даёт в принципе более высокую скорость исполнения, чем сознательный контроль способа реализации на языках низкого уровня. Например, имеются достоверные данные о том, что автоматическое управление памятью более эффективно, чем ручное, уже только при использовании динамического метода (см. сборка мусора)[53], а существует и потенциально более эффективный статический метод (см. управление памятью на основе регионов[en]). Далее, для каждого микроконтекста необходимо распределить регистры с учётом минимизации обращения к памяти, а это требует решения задачи раскраски графа. Такого рода особенностей машинной логики очень много, так что общая информационная сложность возрастает экспоненциально при каждом «шаге на уровень вниз», а компиляция языка высокого уровня может включать десятки таких шагов.

Существует множество стратегий автоматической оптимизации. Некоторые универсальны, другие могут быть применимы лишь к языкам определённой природы, а некоторые зависят от способа использования языка. Примером может служить оптимизация хвостовых вызовов и её частный случай — оптимизация хвостовой рекурсии. Хотя компиляторы многих языков осуществляют оптимизацию хвостовой рекурсии при определённых условиях, лишь некоторые языки способны семантически гарантировать оптимизацию хвостовых вызовов в общем случае. Стандарт языка Scheme требует, чтобы всякая реализация гарантировала её. Для многих функциональных языков она в принципе применима, но лишь оптимизирующие компиляторы её выполняют. В языках вроде Си или С++ она может производиться лишь в определённых случаях и лишь при использовании глобального анализа потока управления.[54]

Языки высшего порядка в большинстве случаев вынуждены исполняться медленнее, чем языки первого порядка. Причины лежат как в самой декомпозиции линейного кода на цепочку вложенных вызовов, так и в вытекающих особенностях низкоуровневого представления функций (см. замыкание) и данных (обёрнутое (англ. boxed), теговое). Однако, существуют техники агрессивной оптимизации программ, позволяющие редуцировать языки высшего порядка до языков первого порядка (см. дефункциализация[en], MLton, Stalin Scheme[en]).

Популярность языков[править | править вики-текст]

Трудно определить, какой язык программирования наиболее популярен, так как значение слова «популярность» зависит от контекста (в английском языке используется термин «usage», имеющий ещё более размытое значение). Один язык может отнимать наибольшее количество человеко-часов, на другом написано наибольшее число строк кода, третий занимает наибольшее процессорное время, а четвёртый наиболее часто служит исследовательской базой в академических кругах. Некоторые языки очень популярны для конкретных задач. Например, Кобол до сих пор доминирует в корпоративных дата-центрах, Фортран — в научных и инженерных приложениях, вариации языка Си — в системном программировании, а различные потомки ML — в формальной верификации. Другие языки регулярно используются для создания самых разнообразных приложений.

Существуют различные метрики для измерения популярности языков, каждая из которых разработана с пристрастием к определённому смыслу понятия популярности:

  • подсчёт числа вакансий, упоминающих язык
  • количество проданных книг (учебников или справочников)
  • оценка количества строк кода, написанных на языке (что не принимает в расчёт редко публикуемые случаи использования языков)
  • подсчёт упоминаний языка в запросах поисковиков

Следует заметить, что высокие оценки по этим показателям не только никак не свидетельствуют о высоком техническом уровне языка и/или оптимизации расходов при его использовании, но и напротив, порой могут говорить об обратном. Например, язык Кобол входит в число лидеров по количеству написанных на нём строк кода, но причиной этому является крайне низкий показатель модифицируемости кода, что делает этот код не повторно используемым, а lecacy-кодом[en] — как следствие, поддержка программ на Коболе в кратковременной перспективе обходится значительно дороже, чем программ на большинстве современных языков, но переписывание их с нуля потребовало бы значительных единовременных вложений, и может сравниваться только с долговременными расходами[55][нет в источнике][56][нет в источнике].

См. также[править | править вики-текст]

Примечания[править | править вики-текст]

  1. Список языков программирования (англ.). Архивировано из первоисточника 22 августа 2011.
  2. Rojas, Raúl, et al. (2000). «Plankalkül: The First High-Level Programming Language and its Implementation». Institut für Informatik, Freie Universität Berlin, Technical Report B-3/2000. (full text)
  3. Computer Languages, 1989, 1. Невидимый конструктор § Создание кодов, понятных человеку, с. 16.
  4. Linda Null, Julia Lobur, The essentials of computer organization and architecture, Edition 2, Jones & Bartlett Publishers, 2006, ISBN 0-7637-3769-0, p. 435
  5. O'Reilly Media. History of programming languages (PDF). Проверено 5 октября 2006. Архивировано из первоисточника 10 мая 2013.
  6. Frank da Cruz. IBM Punch Cards Columbia University Computing History.
  7. Richard L. Wexelblat: History of Programming Languages, Academic Press, 1981, chapter XIV.
  8. Пратт, 1979, 4.6. Сопоставление с образцом, с. 130-132.
  9. Пратт, 1979, 15. Снобол 4, с. 483-516.
  10. Пратт, Зелковиц, 2002, 8.4.2. Сопоставление с образцом, с. 369-372.
  11. François Labelle. Programming Language Usage Graph. SourceForge. Проверено 21 июня 2006. Архивировано из первоисточника 10 мая 2013.
  12. (2006) «The Semicolon Wars». American Scientist 94 (4): 299–303.
  13. Tetsuro Fujise, Takashi Chikayama, Kazuaki Rokusawa, Akihiko Nakase (December 1994). «KLIC: A Portable Implementation of KL1» Proc. of FGCS '94, ICOT Tokyo, December 1994. http://www.icot.or.jp/ARCHIVE/HomePage-E.html KLIC is a portable implementation of a concurrent logic programming language KL1.
  14. Jim Bender. Mini-Bibliography on Modules for Functional Programming Languages. ReadScheme.org (15 марта 2004). Проверено 27 сентября 2006. Архивировано из первоисточника 10 мая 2013.
  15. Сейбел - Кодеры за работой, 2011, Глава 12. Кен Томпсон, с. 414.
  16. Paulson, "ML for the Working Programmer", 1996, с. 213.
  17. Paulson, "ML for the Working Programmer", 1996, с. 1.
  18. Paulson, "ML for the Working Programmer", 1996, с. 9.
  19. 1 2 3 4 5 Appel - A Critique of Standard ML, 1992.
  20. Harper - Practical Foundations for Programming Languages, 2012, Chapter 4. Statics, с. 35.
  21. Mitchel, 2004, 6.2.1 Type Safety, с. 132-133.
  22. Comparison of static code analyzers: CppCat, Cppcheck, PVS-Studio and Visual Studio
  23. Comparing PVS-Studio with other code analyzers
  24. Пратт, 1979, 2.7. Связывание и время связывания, с. 46-51.
  25. Reynolds, "Theories of programming languages", 1998, 12.4 Deriving a First-Order Semantics.
  26. Strachey - Fundamental Concepts, 1967, 3.5.1. First and second class objects., с. 32-34.
  27. 1 2 Strachey - Fundamental Concepts, 1967, 3.5.1. First and second class objects, с. 32-34.
  28. 1 2 SICP.
  29. Harper - Practical Foundations for Programming Languages, 2012, с. 67.
  30. 1 2 Mernik, 2012, с. 2-12.
  31. Ахо, Ульман, 1992.
  32. Joyner, 1996, 2.2 Communication, abstraction and precision, с. 4.
  33. 1 2 3 Ward, 1994.
  34. Paulson, "ML for the Working Programmer", 1996, с. 63-64.
  35. Kernigan about Pascal, 1981.
  36. Joyner, 1996, 3.17 ‘.’ and ‘->’, с. 26.
  37. Paulson, "ML for the Working Programmer", 1996, с. 177-178.
  38. Hudak, 1998.
  39. 1 2 Брукс, 1975, 1995.
  40. Брукс, 1975, 1995, Достижение концептуальной целостности, с. 30.
  41. C.A.R. Hoare — The Emperor’s Old Clothes, Communications of the ACM, 1981
  42. Алан Кэй The Early History of Smalltalk. — Apple Computer, ACM SIGPLAN Notices, vol.28, №3, March 1993.
  43. Greg Nelson. Systems Programming with Modula-3. — NJ: Prentice Hall, Englewood Cliffs, 1991. — 288 с. — ISBN 978-0135904640.
  44. Commentary on SML, 1991, с. vii.
  45. Thomas Noll, Chanchal Kumar Roy Modeling Erlang in the Pi–Calculus. — ACM 1-59593-066-3/05/0009, 2005.
  46. Design Principles Behind Smalltalk
  47. kx: Calibrated performance
  48. Luca Cardelli Typeful programming. — IFIP State-of-the-Art Reports, Springer-Verlag, 1991.
  49. Ward, 1994: «There is a fundamental limit to complexity of any software system for it to be still manageable: if it requires more than "one brainfull" of information to understand a component of the system, then that component will not be understood fully. It will be extremely difficult to make enhancements or fix bugs, and each fix is likely to introduce further errors due to this incomplete knowledge.».
  50. Гласс, 2004.
  51. Czarnecki et al, 2004.
  52. Турчин В.Ф. Эквивалентные преобразования программ на РЕФАЛе: Труды ЦНИПИАСС 6: ЦНИПИАСС, 1974.
  53. B. Zorn. The Measured Cost of Conservative Garbage Collection. Technical Report CU-CS-573-92. // University of Colorado at Boulder. — 1993. — DOI:10.1.1.14.1816.
  54. Ehud Lamm.
  55. Edsger W. Dijkstra. How do we tell truths that might hurt? (EWD498). University of Texas at Austin (2006).
  56. COBOL and Legacy Code as a Systemic Risk | naked capitalism (2016).

Литература[править | править вики-текст]

  • Теренс Пратт. Языки программирования: разработка и реализация = Programming Language Design and Implementation (PLDI). — 1-е издание. — Мир, 1979.
  • Альфред Ахо, Рави Сети, Джеффри Ульман. Компиляторы: принципы, технологии и инструменты. — Addison-Wesley Publishing Company, Издательский дом «Вильямс», 1985, 2001, 2003. — 768 с. — ISBN 5-8459-0189-8 (рус.), 0-201-10088-6 (ориг.).
  • Time-Life Books. Язык компьютера = Computer Languages. — М.: Мир, 1989. — Т. 2. — 240 с. — (Understanding Computers). — 100 000 экз. — ISBN 5-03-001148-X.
  • Альфред Ахо, Джеффри Ульман. Foundations of Computer Science. — Computer Science Press, 1992.
  • Lawrence C. Paulson[en]. ML for the Working Programmer. — 2nd. — Cambridge, Great Britain: Cambridge University Press, 1996. — 492 с. — ISBN 0-521-57050-6 (твёрдый переплёт), 0-521-56543-X (мягкий переплёт).
  • Andrew W. Appel. Modern compiler implementation in ML (in C, in Java). — Cambridge, Great Britain: Cambridge University Press, 1998. — 538 с. — ISBN (ML) 0-521-58274-1 (hardback), 0-521-60764-7 (paperback).
  • Роберт У. Себеста. Основные концепции языков программирования = Concepts of Programming Languages / Пер. с англ. — 5-е изд. — М.: Вильямс, 2001. — 672 с. — 5000 экз. — ISBN 5-8459-0192-8 (рус.), ISBN 0-201-75295-6 (англ.).
  • Вольфенгаген В. Э. Конструкции языков программирования. Приёмы описания. — М.: Центр ЮрИнфоР, 2001. — 276 с. — ISBN 5-89158-079-9.
  • Паронджанов В. Д. Как улучшить работу ума. Алгоритмы без программистов — это очень просто! — М.: Дело, 2001. — 360 с. — ISBN 5-7749-0211-0.
  • Теренс Пратт, Марвин Зелковиц. Языки программирования: разработка и реализация. — 4-е издание. — Питер, 2002. — (Классика Computer Science). — ISBN 978-5-318-00189-5.
  • Martin Campbell-Kelly. From Airline Reservations to Sonic the Hedgehog: A History of the Software Industry. — MIT Press, 2003. — 372 с. — (History of Computing). — ISBN 978-1422391761.
  • John C. Mitchell. Concepts in Programming Languages. — Cambridge University Press, 2004. — ISBN 0-511-04091-1 (eBook in netLibrary); 0-521-78098-5 (hardback).
  • Питер Сейбел. Кодеры за работой. Размышления о ремесле программиста. — Символ-Плюс, СПб. — 2011. — ISBN 978-5-93286-188-2, 978-1-4302-1948-4 (англ.).

Ссылки[править | править вики-текст]