Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

Что нового с 1966 года?

К 1966 году большая часть современного генофонда понятий языка программирования была хорошо известна. У нас были Fortran, Algol 60 и Lisp, которые записывали знакомые концепции, например, локальные переменные, выделенные в стеке, записи, выделенные в куче, if-then-else и итерация, именованные процедуры и функции (включая рекурсию), статическая и динамическая типизация, а также обзор за 1967 и 1968 гг. дали нам ссылки на структурированные данные с именованными компонентами (Алгол 68) и объектами и подтипами (Simula 67) [Ландин подчеркнул, что λ-исчисление является теоретической основой для языков программирования, и не упомянул более богатое понятие области видимости, которое добавляет объектная ориентация]. Историческое значение COBOL не следует преуменьшать: с 1959 года он предоставил способ описания, чтения, печати и манипулирования текстом и двоичными записями фиксированного формата, которые преобразовались в языковой дизайн (записи) и базы данных.

Сохраняются некоторые расхождения между языками и растениями: сравните программные парадигмы (функциональные, реляционные, объектно-ориентированные и т.д.) с четырьмя основными группами растений (цветущие растения, хвойные, мхи и папоротники). Другой пример различий языков программирования — статическая и динамическая типизация. Современная мода в программировании в реальном мире вновь, похоже, включает динамическую типизацию для практического программирования (свидетельство использования JavaScript, Python, Ruby и т.п.), часто выступая в качестве связующих языков для соединения библиотек, написанных на статически типизированных языках.

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

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

2.1. Задачи, инструменты и команды

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

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

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

Размер программных систем также значительно увеличился. Начиная с систем, работающих на одном компьютере, распределенные системы теперь могут работать на сотнях или тысячах компьютеров в дата-центрах по всему миру и содержать десятки миллионов строк кода. Языковые реализации развивались, чтобы помочь людям справиться с этой сложностью, например предоставляя сложную поддержку для пакетов и модулей, включая динамическую загрузку и управление версиями. Некоторые языки (или среды выполнения) предоставляют их в качестве основных функций, например, Java и C# предоставляют возможность динамической загрузки классов из jar-файлов или сборок.

Для других языков были разработаны сопутствующие инструменты, которые помогают управлять и составлять зависимости, например, «Npm» (менеджер пакетов узлов) для Node.js.

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

Способ работы команд по разработке программного обеспечения также изменился. Подходы для одного человека сменились процессами разработки в стиле водопада для управления программными проектами для нескольких человек, которые, в свою очередь, были в значительной степени заменены (для всех систем, кроме наиболее критичных для безопасности) более гибкими подходами, такими как eXtreme Programming [4]. Гибкие методы требуют инструментов анализа, тестирования и преобразования для поддержки частых и надежных изменений и обеспечения быстрой обратной связи, необходимой для продуктивности разработчиков. Языки, где они хорошо поддерживаются, имеют естественное преимущество в экосистеме.

Наконец, одно из самых больших изменений с 1967 года было в инструментах, которые мы используем для поддержки программирования — конечно, благодаря значительному увеличению вычислительной мощности за это время. Хотя классические редакторы, компиляторы и (статические) компоновщики все еще присутствуют, произошел взрыв в новых формах поддержки инструментов и их использовании: интегрированные среды разработки (IDE), включая поддержку анализа и рефакторинга кода, системы контроля версий, генераторы тестов, а также с инструментами для динамического связывания с кодом и данными вне проекта. Они развиваются намного быстрее и в значительной степени независимо от огромных баз кода в программных системах; последние, как правило, развиваются постепенно, чтобы фиксировать изменения в функциях программного обеспечения (и даже медленнее, отражая эволюцию языка программирования). Мы утверждаем, что поддержка соответствующего инструмента является сильным фактором в приспособленности к языку программирования и, следовательно, к выбору языка в данном программном проекте.

2.2 Системное программирование и рост C

Успех языка программирования C и Unix неразрывно связан. Ричи [35] пишет: «C возник в 1969–1973 годах, параллельно с ранним развитием операционной системы Unix; самый творческий период произошел в 1972 году». С точки зрения экосистемы, C имеет большое значение, поскольку он успешно опередил конкурирующие языки и стал почти единственным широко используемым языком системного программирования. Габриэль отмечает, что «Unix и C являются совершенными компьютерными вирусами» [11], утверждая, что их дизайн «хуже-лучше», где простота реализации предпочтительнее других функций, таких как полная корректность и простота интерфейса, делает Unix и C такими легко портируемыми, что они заражают как вирусы практически все системы. Ссылаясь на естественный отбор, Габриэль пишет: «хуже, даже в форме чучела, он имеет лучшие характеристики выживания, чем более правильные вещи».

Рассчитанный на относительно низкий уровень системного программирования C предоставляет механизмы для детального и эффективного управления памятью, необходимого для создания операционных систем и драйверов устройств или для программирования сред с ограниченными ресурсами, таких как встроенные системы. C обычно является первым языком более высокого уровня, чем поддерживаемый в новой архитектуре ассемблер. Долгое время (и все еще в значительной степени) рассматриваемый как неизбежная цена за его высокую производительность, C является небезопасным языком: ошибки времени выполнения, такие как переполнение буфера, обычно не обнаруживаются языковыми реализациями, поэтому программа может продолжить выполнение после ошибочной операции. Это обеспечивает широкие возможности для атак, например, похищение потока управления и чтение предположительно конфиденциальных данных. Ботаническая аналогия может заключаться в распространенности кактусов в пустынях, когда некоторые предпочитают орхидеи как более красивые и лишенные колючек; мы должны либо отрегулировать нишу — требуя красоты (или безопасности), внести больший вклад в физическую форму благодаря вмешательству человека, либо создать новый вид растений (или язык), который лучше соответствует существующей нише, имея желаемые характеристики.

Безусловно, C был одним из наиболее влиятельных языков программирования, влияющим на синтаксис и, в некоторой степени, семантику C++, Java и C# и формирующим основу для языков параллельного программирования, таких как CUDA (собственный язык NVIDIA для модуля обработки графики (GPU) и OpenCL (стандартная альтернатива). Кроме того, C продолжает очень широко использоваться; возможно, гораздо больше, чем следует, учитывая его небезопасный характер. Мы вернемся к этому пункту в разд. 5.1.

2.3 Объектно-ориентирование и рост Java

Объектно-ориентированная парадигма, в которой доменные объекты представлены в виде объектов, которые обмениваются сообщениями, стала доминирующим стилем разработки коммерческого программного обеспечения в 1990-х годах. Основываясь на оригинальных идеях Кея, воплощенных в Smalltalk, появился ряд очень влиятельных языков, в том числе Delphi, Eiffel, Oberon, Self и Simula 67. Они повлияли на дизайн того, что в настоящее время является наиболее широко используемыми объектно-ориентированными языками — Java, C++ и C# — которые продолжают процветать, в то время как многие из этих более ранних языков сейчас вымерли или ограничены сообществами энтузиастов. (Мы признаем, что некоторые пуристы подвергают сомнению, действительно ли такие языки, как Java, C++ и C#, действительно запрещены — например, они не проходят так называемый «тест Инголса» [30, Sect. 11.6.4], — но их широко считают принадлежащими к объектно-ориентированной парадигме).

Мы также видим это влияние на рост инструментальных средств, поскольку современная IDE Eclipse имеет свои корни в VisualAge для Java от IBM, которая, в свою очередь, возникла из VisualAge для Smalltalk. Важной движущей силой раннего распространения Java была его философия «пиши один раз, работает везде», согласно которой компилятор Java генерирует код, который может выполняться на любой совместимой реализации виртуальной машины Java (JVM).

Поддержка JVM веб-браузерами и популярность «апплетов», встроенных в веб-страницы, способствовали принятию Java. Его чистый дизайн и безопасное исполнение сделали его популярным языком обучения в университетах.

Другие функции также способствовали внедрению: Java, C++ и C# обеспечивали поддержку исключений, удовлетворяя практическую потребность в основных языках для захвата этой идиомы. Java и C# приняли идеи, появившиеся в таких языках, как Lisp и Smalltalk, — автоматическое управление памятью (сборка мусора) и идея управляемой среды выполнения, абстрагирующейся от базовой операционной системы (или браузера!). Сохраняется напряжение между C/C ++, с их ручным размещением в памяти и связанным с этим отсутствием безопасности, и Java и C#, которые, хотя и безопасны, могут быть проблематичными в среде с ограниченными ресурсами или в режиме реального времени.

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

2.4. Веб-программирование, возрождение динамической типизации и рост JavaScript

Хотя некоторые ранние языки программирования были динамически типизированы, в частности Lisp, в 70-х, 80-х и 90-х годах основное внимание уделялось языкам статического типа с более сильными и богатыми системами типов. В промышленности языки с относительно сильной статической типизацией также становятся все более популярными, причем языки с динамической типизацией в основном ограничены сценариями и вводным программированием.

С тех пор огромным изменением стала важность программирования для Интернета через такие языки, как JavaScript и PHP, которые динамически типизируются. Веб-программирование настолько распространено, что JavaScript теперь является одним из наиболее широко используемых языков программирования. С точки зрения языкового дизайна это несколько иронично. Хотя много исследований высокого класса были посвящены языкам программирования с динамической типизацией (например, в частности, работа с системами, такими как CLOS [13], и построение на таких языках, как Self [47]), фактически Брендан Эйх разработал и реализовал JavaScript (тогда названный Mocha) за 10 дней, по-видимому, не прибегая к этим исследованиям [42].

Другие динамически типизированные языки разработали сильные последователи, включая Python и Ruby. Оба являются языками общего назначения, но Python стал особенно популярен среди ученых [33], и основным направлением внедрения Ruby стала среда Rails Хайнемайера Ханссона [36], предназначенная для облегчения программирования веб-приложений на стороне сервера и простой интеграции базы данных. Далее мы рассмотрим популярность динамически типизированных языков в разделе 5.2.

2.5 Функциональные языки программирования

Функциональные языки, где абстракция функций и применение являются основным режимом структурирования, зародились в Лиспе. В течение многих лет функциональные языки программирования жили в «гетто энтузиастов», привлекая сильных сторонников к конкретным областям программирования, но их отвращение к изменению существующих значений было слишком большим шагом для многих основных программистов. Однако в последнее время их идеи, похоже, становятся мейнстримом. Два наиболее влиятельных функциональных языка, Haskell и ML в настоящее время являются одними из наиболее широко используемых функциональных языков (с диалектом OCaml, пользующимся наибольшим спросом в случае ML), и оба языка используются в приложениях для количественной (долевой) торговли в банках, которые утверждают, что эти языки позволяют их аналитикам писать код быстрее и правильнее [https://adtmag.com/Ramel0911]. Одно из оправданий этого возрождения заключается в том, что параллелизм и мутация кажутся программистам трудными для совместного использования в больших системах, требующих подверженных ошибкам блокировок или трудно документируемых предположений целой программы о том, когда и как структуры данных могут быть изменены.

Функциональные языки также вдохновили так называемые «мультипарадигмальные» языки, главным образом F# и Scala, оба из которых имеют первоклассные функциональные концепции; а они, в свою очередь, были включены в основные объектно-ориентированные языки, в частности, расширения LINQ для C# и лямбда-выражения в Java 8 и C++11.

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

2.6. Системы гибкого типа

На момент написания статьи Ландина и, по сути, на протяжении большей части последующего десятилетия существовал разрыв между, с одной стороны, динамически типизированными языками, такими как Lisp, которые проверяли типы во время выполнения за счет снижения эффективности выполнения и непредвиденных ошибок и, с другой стороны, статически типизированных языков, дающих повышенную эффективность и возможность устранения ошибок типов во время компиляции (даже если в них есть дыры, такие как в C и Algol 60). Однако такие системы статического типа часто были невыразительными; шутка во время докторской диссертации одного автора состояла в том, что, например, в Паскале нужно было писать функции, отличающиеся только типами, для нахождения длин целочисленных списков и списков строк, чтобы компилятор мог генерировать дублированный код. В конце 1970-х годов появились параметризованные типы и полиморфно типизированные (или «универсальные») функции для работы над ними. Язык ML (первоначально являвшийся частью системы доказательства теорем в Эдинбургском LCF) был здесь чрезвычайно полезен. В некотором смысле ML был почти точно «ISWIM с типами», хотя и без синтаксиса с отступом. Типы продолжают расти в выразительности, причем системы типов в Java и C# включают в себя три формы полиморфизма (универсальные, перегрузочные и ограниченные) и все в одном языке.

Дисциплины с более тяжелым типом, такие как языки с зависимой типизацией (например, Agda, Coq), остаются в стороне от основного потока, несмотря на высокую точность, которую они предлагают, и их потенциальную связь с верификацией программ (см. раздел 6.2).

Существует аргумент, что очень точные типы могут в конечном итоге раскрыть подробности о внутренностях системы, и что стремление к определенной структуре типов может оказать более непосредственное влияние на структуру программы, чем это может быть на самом деле желательно; они были названы «Программа видимого водопровода» и «Проблема пересечения», соответственно [9].

2.7. Параллельность и рост многоядерности

Появление многоядерных процессоров x86 с 2005 года, оглядываясь назад, очень разрушительно для языков программирования. Хотя параллельная обработка не была новой (например, High Performance Fortran предлагал относительно сложную языковую поддержку параллельного программирования [20]), многоядерная (и более поздняя GPU) технология вынуждала обычных программистов хотеть, чтобы их язык поддерживал ее. Этот мир немного напоминает мир Ландина в 1966 году - существует множество языковых функций и библиотек, предлагающих поддержку различных аспектов параллелизма. Выделенные языки и библиотеки включают Cilk Plus, Threading Building Blocks и OpenMP для многоядерных процессоров, а также OpenCL, CUDA, Vulkan, Metal и RenderScript для целевых графических процессоров.

Внутри одного языка может быть много механизмов, с помощью которых можно использовать параллелизм: в Java можно использовать параллельные потоки, службу исполнителя, создавать собственные объекты потоков; в Haskell также существует несколько подходов [27].

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

2.8. Проблемно-ориентированные языки программирования

Как и во времена Ландина, было создано много языков для решения проблем в определенных областях. Хотя полнота по Тьюрингу означает, что мы должны иметь возможность применять любой язык общего назначения к любой задаче программирования, специфичные для предметной области языки часто предлагают более удобный синтаксис и поддержку библиотек, чем это было бы возможно для основного языка, с примерами, включающими электронные таблицы, SQL, MATLAB и R, наряду с языками сценариев для движков компьютерных игр и графики.



Перевод: Д.Ю.Караваев. 14.11.2019

Опубликовано: 2019.11.25, последняя правка: 2019.11.25    21:57

ОценитеОценки посетителей
   ██████████████████████████████████████████ 1 (100%)
   ▌ 0
   ▌ 0
   ▌ 0

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

●  О превращении кибернетики в шаманство

●  Про лебедей, раков и щук

●  О русском ассемблере

●  Арифметика синтаксиса-3

●  Концепция владения в Rust на примерах

●●  Концепция владения в Rust на примерах, часть 2

●●  Концепция владения в Rust на примерах, часть 3

●  Суть побочных эффектов в чисто функциональных языках

●  О неулучшаемой архитектуре процессоров

●  Двадцать тысяч строк кода, которые потрясут мир?

●  Почему владение/заимствование в Rust такое сложное?

●  Масштабируемые архитектуры программ

●  О создании языков

●●  Джоэл Спольски о функциональном программировании

●  Почему Хаскелл так мало используется в отрасли?

●  Программирование исчезнет. Будет дрессировка нейронных сетей

●  О глупости «программирования на естественном языке»

●  Десятка худших фич C#

●  Бесплатный софт в мышеловке

●  Исповедь правового нигилиста

●  ЕС ЭВМ — это измена, трусость и обман?

●  Русской операционной системой должна стать ReactOS

●  Почему обречён язык Форт

●  Программирование без программистов — это медицина без врачей

●  Электроника без электронщиков

●  Программисты-профессионалы и программирующие инженеры

●  Статьи Дмитрия Караваева

●●  Идеальный транслятор

●●  В защиту PL/1

●●  К вопросу о совершенствовании языка программирования

●●  Опыт самостоятельного развития средства программирования в РКК «Энергия»

●●  О реализации метода оптимизации при компиляции

●●  О реализации метода распределения регистров при компиляции

●●  О распределении памяти при выполнении теста Кнута

●●  Опыты со стеком или «чемпионат по выполнению теста Кнута»

●●  О размещении переменных в стеке

●●  Сколько проходов должно быть у транслятора?

●●  Чтение лексем

●●  Экстракоды при синтезе программ

●●  Об исключенных командах или за что «списали» инструкцию INTO?

●●  Типы в инженерных задачах

●●  Непрерывное компилирование

●●  Об одной реализации специализированных операторов ввода-вывода

●●  Особенности реализации структурной обработки исключений в Win64

●●  О русском языке в программировании

●●  Формула расчета точности для умножения

●●  Права доступа к переменным

●●  Заметки о выходе из функции без значения и зеркальности get и put

●●  Модификация исполняемого кода как способ реализации массивов с изменяемыми границами

●●  Ошибка при отсутствии выполняемых действий

●●  О PL/1 и почему в нём не зарезервированы ключевые слова

●●  Не поминайте всуе PL/1

●●  Скорость в попугаях

●●  Крах операции «Инкогнито»

●●  Предопределённый результат

●●  Поддержка профилирования кода программы на низком уровне

●●  К вопросу о парадигмах

●  Следующие 7000 языков программирования

●●  Что нового с 1966 года?

●●  Наблюдаемая эволюция языка программирования

●●  Ряд важных языков в 2017 году

●●  Слоны в комнате

●●  Следующие 7000 языков программирования: заключение

Компьютерный юмор

Новости и прочее




Последние отзывы

2024/03/19 02:19 ••• Ivan
Энтузиасты-разработчики компиляторов и их проекты

2024/03/18 23:25 ••• Автор сайта
Надёжные программы из ненадёжных компонентов

2024/03/18 22:44 ••• Автор сайта
О многократном резервировании функций

2024/03/17 17:18 ••• Городняя Лидия Васильевна
Раскрутка компилятора

2024/03/10 18:33 ••• Бурановский дедушка
Русской операционной системой должна стать ReactOS

2024/03/07 14:16 ••• Неслучайный читатель
«Двухмерный» синтаксис Python

2024/03/03 16:49 ••• Автор сайта
О неправомерном доступе к памяти через указатели

2024/02/28 18:59 ••• Вежливый Лис
Про лебедей, раков и щук

2024/02/24 18:10 ••• Бурановский дедушка
ЕС ЭВМ — это измена, трусость и обман?

2024/02/22 15:57 ••• Автор сайта
Русский язык и программирование

2024/02/19 17:58 ••• Сорок Сороков
О русском языке в программировании

2024/02/16 16:33 ••• Клихальт
Избранные компьютерные анекдоты

2024/02/10 22:40 ••• Автор сайта
Все языки эквивалентны. Но некоторые из них эквивалентнее других