Арифметика синтаксиса-3
В своё время Сергей Свердлов, задаваясь вопросом о простоте и сложности языков программирования, решил измерить алгеброй гармонию.
И приблизительно оценил меру сложности языков через число синтаксических правил в них (статья «Арифметика синтаксиса»).
Евгений Александрович Зуев вполне заслуженно подверг критике методику оценки и предложил другую (статья «Арифметика синтаксиса-2»).
Если коротко, то его замечания касались того, что все грамматики перед измерением надо предварительно привести к единообразному виду.
Логичнее всего выбрать в качестве формальной нотации входной язык генератора распознавателей YACC/Bison. И потом делать подсчёт.
|
Иллюстрация Сергея Свердлова к статье «Арифметика синтаксиса»
|
Чья оценка синтаксиса нас интересует?
Но оба автора, на мой взгляд, забыли (или не посчитали существенным) о том, что оценка сложности языка зависит от того, кто её делает.
У вычислительной машиной одна оценка, а у человека она совсем другая.
Спроси программистов: «Какой язык проще?», и у каждого будет своя собственная субъективная оценка.
Эти оценки можно, конечно, получить в ходе соцопросов.
Но в результете получим, скорее всего, кривую распределения Гаусса, а не точную единственную цифру, которую даёт нам Сергей Свердлов.
И которую может быть выдаст Евгений Зуев, если пересчитает всё по своей методике.
Компилятор читает символы слева направо, а человек видит текст или его фрагменты целиком, одновременно. У них разные методы чтения и анализа.
Соответственно и их оценка разнится. Количество правил языка влияет на сложность компилятора.
Человек зачастую не видит разницы в сложности или его мнение прямо противоположно мнению вычислительной машины.
Возьмём такой пример:
a b * c + // посфиксная запись
a * b + c // инфиксная запись.
A IF B ELSE C THEN // условный оператор в Форте
if (A) {B} else {C} // условный оператор в Си
Инфиксные записи человеку знакомее в силу традиций в образовании: нас учили именно так.
Но для компьютера разбор постфиксных записей проще: меньше правил.
У инфиксных операций есть приоритеты, что увеличивает количество правил языка.
Постфиксные операции прекрасно обходятся без приоритетов.
Можно констатировать, что человек и машина предпочитают разные формы записи.
У них разные суждения о сложности, их предпочтения противоположны.
Язык Форт, к слову, не слишком популярен в том числе и потому, что у него простая для компилятора, но неудобная для человека форма записи.
Или взять тернарную операцию вида x < y < z : она не вызывает затруднений у человека.
Но она далеко не в каждом языке есть. Не от того ли, что она сложновата?
Вывод: сложность в человеческом измерении и сложность в машинном слишком отличаются, чтобы видеть в них сильную корреляцию.
Увеличение размера программы приведёт к увеличению времени её компиляции.
Наверное, зависимость близка к линейной. Но компилятору, по большому счёту, всё равно, какую программу подадут ему на вход.
А вот для человека сложность понимания программы при её увеличении растёт далеко не линейно.
Человек может просто захлебнуться в море нахлынувшей на него информации.
Если язык имеет малое число правил, то это даёт приятные бонусы.
-
Во-первых, трудоёмкость написания компилятора уменьшается. Как следствие, увеличивается число конкурирующих компиляторов, что приводит к богатству выбора.
А вот постоянное увеличение правил в C++ приводит к увеличению сложности компиляторов, трудоёмкости их разработки и, как следствие, к уменьшению и без того небогатого выбора.
-
Во-вторых, увеличивается надёжность компилятора в силу его простоты.
-
Ну и в-третьих, меньший объём учебников, справочников и руководств. Везде сплошная экономия.
Минимализм синтаксиса — путь к совершенству?
В статье Сергея Свердлова особо выделяется язык Оберон-2.
Очень компактный и простой, имеет наименьшее среди рассматриваемых языков число лексем.
Спартанский синтаксис, порою скудный, как еда монахов. Пример того, когда бедность является добродетелью.
Почти идеальный язык?
А вот находка в тему из всемирной паутины, образчик чёрного юмора:
Злые языки утверждают, что число правил в виртовских языках уменьшается по причине прогрессирующего склероза.
Когда профессор забывает, для чего нужно какое-то правило, он его просто выкидывает.
Постепенно язык Вирта уменьшается, приближаясь по своей простоте к машине Тьюринга.
Когда они сравняются, то это будет полная победа Альцгеймера. Но я сам в это не верю и вам не советую.
Но насколько востребована у программистов при выборе языка именно этот критерий — минимальное количество правил?
Является ли уменьшение числа лексем желанной целью или эту цель никто и не держит в голове?
Пример из истории Второй мировой войны.
На британских транспортных судах, перевозивших через Атлантику грузы, стояли зенитные установки, которые защищали от немецкой авиации.
Однажды один из высоких британских чинов задался вопросом: «А сколько немецких самолётов было сбито этими зенитками?».
И оказалось, что ни одного — за довольно длительный период.
«Тогда какая от них польза?» — и распорядился снять зенитки с судов и передать их на фронт, где они нужнее: там у них больше шансов сбить самолёты врага.
После такого решения резко увеличилась потеря судов и грузов от налётов авиации.
Если раньше зенитные установки хотя бы отпугивали вражеские самолёты, держали их на расстоянии, то теперь они могли без опасений вести прицельную бомбардировку.
Какой вывод из этого? Была неверно выбрана цель.
Вместо цели «наименьший ущерб своим судам и грузам» была выбрана цель «наибольший ущерб противнику».
Вторая цель — ложная.
Что является целью программирования? Написание программ 1) которые по функциональности и качеству соответствуют поставленной задаче 2) наименее трудоёмким способом.
В этом заключается истинная цель. А какое отношение к этому имеет «наименьшее количество правил в языке»? Никакое.
Язык в данном случае — просто инструмент. А качество инструмента тоже плоховато коррелирует с числом правил языка.
Поищем примеры, когда увеличение числа правил языка улучшают язык.
Модула-3 отличается от Модула-2 наличием предварительных определений функций.
По этой причине стала возможной компиляция программ в один проход.
Это несёт в себе некоторые положительные моменты.
С точки зрения простоты и количества правил языка – это шаг назад.
В языке D наряду с обычными функциями есть ещё и чистые функции.
Объявление функции чистой гарантируют отсутствие у неё побочных эффектов.
Попытка употребления внутри неё иных функций, кроме чистых, будет пресечена компилятором.
Несмотря на очевидную пользу таких
функций, число правил в языке D от этого вырастает.
Контрактное программирование – это благо или зло?
Этот механизм дополнительного обеспечения надёжности может быть весьма полезен.
Конечно, проверка параметров функции как на входе, так и на выходе может быть обеспечена обычными операторами.
Но если эти проверки делаются чистыми функциями, не имеющими без побочных эффектов, то их, по идее, можно отключить опциями компилятора – после того,
как программа показала свою надёжность и есть необходимость увеличить её скорость и компактность.
Если же такая проверка делается обычными «if», то как компилятор их отключит? Как отличить обычный «if» от «if», которым делаем проверку контракта?
В области ЯП существует прогресс так же, как он существует вообще в любой отрасли науки и техники. Возрастающая сложность преодолевается специализацией.
Мы согласны на больший комфорт, даже если для этого необходимо потрудиться над своими знаниями.
Например, научиться водить автомобиль, выучить правила дорожного движения, или овладеть более сложным языком программирования.
Человеческий мозг, к счастью, не переполняется от новых знаний, он не просит очистить диск от старой информации, чтобы пополниться новой.
Поэтому программисты чаще всего знает не один язык программирования.
Выводы
На самом деле измерение сложности языка — достаточно полезное занятие. Полученные цифры будут полезны разработчику компилятора этого языка.
Только надо помнить, что это сложность не «вообще», а «в частности»: это сложность для компилятора, а не сложность, с которой сталкивается программист при изучении и использовании языка.
Стремление же к уменьшению правил языка в целом разумно, если не вступает в противоречие с включением в язык важных средств.
А вот стремление напичкать язык всеми возможными фичами ни к чему хорошему не приводит.
Опубликовано: 2022.11.22, последняя правка: 2024.03.02 17:18
Отзывы
✅ 2022/12/04 13:58, Автор сайта #0
Простота инструмента имеет два различных аспекта: простота изучения его функций и простота его использования после осознания этих функций. Это цитата из статьи «Истоки Python». Какой-то прямой взаимосвязи между простотой инструмента и минимальным количеством правил автор статьи не заметил. Оттуда же:минималистский язык, хотя и прост в изучении, может усложнить задачу программиста. Мало того, что требуется больше строк кода, так ещё и написание корректного кода становится все сложнее по мере увеличения размера программы. И наоборот, если попытаться сделать что-то похожее на швейцарский нож и включить в него все мыслимые и немыслимые функции, получится сложный язык, который будет трудно изучить, но, несомненно, легко использовать. Или не будет? Возможно, не будет. ✅ 2022/12/12 18:06, void #1
Почему Ваза утонул, а С++ — всё ещё на плаву? Ваза был спроектирован не то, чтобы комитетом, а некоторым королём, который политически настоял на своих благоглупостях, выглядящих красиво — но ухудшающих остойчивость. Потому и утонул. Из-за не способствующих плаванию проектных компромисов. Утонули ли языки по настоящему высокого уровня (выше, чем С++ — например, языково-ориентированное программирование), спроектированные комитетами по стандартизации? Когда определённую модную возможность включали в язык не вполне понимая как и главное, зачем её реализовывать?
Когда никто в принципе не занимался вопросом анализа минималистичности, но не примитивизма используемых концепций, вопросами как эти разнородные концепции должны работать вместе, синергичностью, что ли и поиском необходимых, подходящих в смысле "масштабируемой архитектуры программ", составимых каким-то образом концепций?
Я вот не уверен полностью что на текущий момент такие ЯВУ как Ада, Common Lisp да тот же PL/I или там Алгол — полностью, окончательно, затонули. Если уж уподоблять их С++. Потому что там концепций в общем достаточно, да и логика их структурирования прослеживается в стандарте языка. Есть компиляторы, библиотеки, пакетные менеджеры, в общем — какая-никакая инфраструктура и субкультура.Стремление же к уменьшению правил языка в целом разумно, если не вступает в противоречие с включением в язык важных средств. А вот стремление напичкать язык всеми возможными фичами ни к чему хорошему не приводит. Вспоминается такая вот анкета-памятка автору своего языка программирования: https://myrlang.org/lang-checklist или "универсальные резолюции Полыхаева": отправлять, но не писать. Сравнение языков программирования, по необходимым концепциям и критерии для такого сравнения, критерий качества ЯП появились в книге КАУФМАН В.Ш. "ЯЗЫКИ ПРОГРАММИРОВАНИЯ концепции и принципы", также перекликается с книгой А.Л. Фуксмана "Технологические аспекты создания программных систем" про расслоенное программирование, вертикальное слоение — по сути, многоаспектное программирование за 17 лет до формирования самого термина "аспектно-ориентированное программирование":Критерии ценности языка программирования
Первичные критерии: 1. Надежность, соответствие программы назначению, отсутствие ошибок 2. Экономный расход объема памяти 3. Экономный расход времени процессора 4. Низкие затраты на создание программы 5. Низкая трудоемкость эксплуатации программы 6. Низкие затраты времени на создание программы
Вторичные критерии: 1. Познаваемость программы 2. Производительность программирования 3. Адаптируемость программы 4. Защищенность от ошибок 5. Связь с реализацией 6. Изучаемость языка
Вторичные критерии — подробно
Познаваемость программы 1. Расшифрованность текста программы 2. Мнемоничность обозначений языка 3. Близость языка к естественному 4. Произносимость конструкций языка 5. Лаконичность конструкций языка Производительность программирования 1. Одновременное создание частей программы разными исполнителями 2. Укрупненный характер информационных объектов и операций
Адаптируемость программы 1. Адаптируемость программы к изменению задачи 2. Адаптируемость программы к изменению оборудования, на котором она выполняется 3. Машинная независимость языка
Вторичные критерии — подробно (2)
Защищенность от ошибок 1. Возможность обнаружить описки при компиляции 2. Возможность статического контроля несоответствия между планируемыми и фактическими свойствами информационных объектов.
Связь с реализацией 1. Возможность простой машинной реализации языковых средств. 2. Ясное ощущение программистом стоимости (по времени и по памяти) языковых конструкций. 3. Низкая трудоемкость эффективной реализации языка.
Изучаемость языка 1. Независимость (ортогональность) конструкций языка. Возможность менять язык в отношении одной конструкции, не затрагивая прочих. 2. Логическая однородность языка. Аналогичное изображение идентичных функций. Эти критерии характеристические, универсальные, объективные и ортогональные. В какой-то степени, они более относятся к инфрастурктуре: системе программирования как среде, некоторой экосистеме. А не столько к конкретной идее языка высокого уровня, его реализации, ну или синтаксису.
В этом смысле, подобную экосистему можно было бы выстроить почти на любом: подходящем ЯВУ, обеспечивающем составимость нужных концепций. Либо вообще языково-нейтрально, как некоторую метасистему, метатехнологию. Например, (мета)технологию грамотного (литературного) программирования.
Что любопытно — вот с одной стороны, - А. Л. Фуксман пишет про "Технологию вертикального слоения — алгоритм", инструментальный комплекс, оформление программы слоя, некоторый текстовый DSL разметки гнёзд и слоев, "расширяемых программ" и гнёзд расширения по М. Горбунову-Посадову;
с другой стороны,- А. П. Ершов пишет про "лексикон программирования"
- Лео Броуди пишет про лексикон слов диалекта Форта и "Форт как образ мышления"
- Дональд Эрвин Кнут в "TeX — the book and the program пишет про грамотное программирование (в противовес безграмотному, illiterate)
- кто-то ещё про грамотное развертывание — то есть, Literate DevOps
При этом они, как тот герой Мольера — все разговаривают прозой (только не подозревая об этом): то есть, говорят по сути об одном — о некоторой метасистеме, метатехнологии программирования. Странно, что объединить их вместе пока что никто не додумался.
Язык Алгол вообще породил много интересных подходов и метатехнологий: Рефал В.Ф. Турчина и суперкомпиляцию (и сами понятия метасистемный переход, проекции Футамуры-Турчина), технология А.Н. Терехова и технология А.Л.Фуксмана про "расслоенное программирование", "вертикальное слоение".✅ 2022/12/12 18:17, void #2
Возьмём такой пример: a b * c + // постфиксная запись a * b + c // инфиксная запись. A IF B ELSE C THEN // условный оператор в Форте if (A) {B} else {C} // условный оператор в Си продолжу: (+ c (* a b)) # префиксная запись в Лиспе, S-выражения (foo bar (baz xyzzy yadda)) # префиксная запись в лиспе, S-выражения
FOO bar baz(xyzzy,yadda) ; # ??? ещё-какая-то универсальная форма записи в PL/I foo(bar,baz(xyzzy,yadda)); # макросы в Dylan, D-выражения <?????> # Алгол-выражения то есть: синтаксически если не пытаться всё записать, именно на Yacc/Bison можно реализовать более простой парсер, рекурсивный нисходящий (возможно, многопроходный, как в PL/I), которым разбирать все эти X-выражения будет проще. Более того, их можно транслировать из разных входных синтаксисов в некий универсальный (например, префиксных S-выражений).
То есть: реализация транслятора сводится к реализации такого мини-языка, который умеем эффективно кодогенерировать, и в том числе можно взять готовые. И в принципе весь "компилятор" можно реализовать на скорую руку через достаточно расширяемую макросистему. Хотя вот ещё, был язык PL/I. А был и макроязык, ML/1. Универсальный и полный по Тьюрингу, кроссплатформный и переносимый. И довольно гибко расширяемый.✅ 2022/12/12 18:32, void #3
Поищем примеры, когда увеличение числа правил языка улучшают язык. Модула-3 отличается от Модула-2 наличием предварительных определений функций. По этой причине стала возможной компиляция программ в один проход. Это несёт в себе некоторые положительные моменты. С точки зрения простоты и количества правил языка — это шаг назад. Модула-3 объектно-ориентированный, Модула-2 системный, Оберон-2 прикладной, Ада — системный. Дело не только в простоте и количестве правил вообще — а в какой-то подходящей структуре нужных правил.
Вот например, в Аде добавили task, task types в язык и далее получили необходимость делать synchronized, limited, controlled types и классы памяти. Где теоретически достаточно умный компилятор мог бы сам проанализировать на дедлоки и лайвлоки, ну или организовать механизм рандеву и мониторы/семафоры; или далее реализовать lock-free алгоритмы на limited, controlled types.
В C++ например задачи выкинули из языка в библиотеку (вернув в последних версиях стандарта std::threads и coroutines). В итоге компилятор просто не имеет нужной информации о том, является этот блок кода последовательным или параллельным. И не может далее оптимизировать.
В языке Pony с акторной моделью сделали типы ссылок разных классов, для анализа возможности lock-free; в языке Rust на семантике владения можно написать lock-free и обернуть процедурными AST макросами.
В языке Оберон-2 с многопоточностью ничего не делали; в Oberon-2 BlackBox Component Pascal — это можно было реализовать как библиотеку; в A2 Active Oberon — активные объекты встроили в язык; в Zonnon в язык встроили для компонентного программирования некие составимые протоколы.С точки зрения простоты и количества правил языка — это шаг назад. С точки зрения информации для достаточно умного компилятора, умеющего оптимизировать многопоточное — наоборот, шаг вперёд. С точки зрения языково-ориентированного программирования и лексиконов программирования — вообще топтание на месте.✅ 2022/12/12 18:55, void #4
Только надо помнить, что это сложность не «вообще», а «в частности»: это сложность для компилятора, а не сложность, с которой сталкивается программист при изучении и использования языка. Сложность, с которой сталкивается программист при изучении языка — вообще, по большей части относится к описанию реализации, её инфраструктуры, инструментальной технологии, и прочей метатехнологии, чем к какой-то умозрительной сложности языка как синтаксиса (а ещё есть семантика).
К примеру, в детстве я начал изучать языки программирования почти параллельно:- PL/I по книжке Джермейн, "Программирование под OS/360"
- Фортран по какой-то синей книжке: описывалась старая реализация языка, не описывалась инфраструктура и библиотеки
- Паскаль по какой-то синей книжке: описывался ISO Pascal не описывалась инфраструктура и библиотеки
- язык Си по книжкам М. Болски как справочник-разговорник на Си карманного формата, и Керниган и Пайк про "Практику программирования"
Угадайте, почему Паскаль и Фортран я тогда воспринимал как языки непрактичные? А Си или PL/I — наоборот, практичные и низкоуровнево эффективные.
Для меня лично это послужило каким-то маяком: да, интересная идея. Нужно найти почитать ещё книжек. Потому что имеющаяся на тот момент информация была сумбурна и противоречива, её было явно недостаточно.
Когда я потом столкнулся с жёлтой книжкой про Паскаль и структуры данных (стек там рисовали комиксами, наглядно на картинках) — моё понимание практичности языка Паскаль немного увеличилось. И ещё раз увеличилось, когда прочитал книжку по Турбо-Паскалю. Когда я потом написал себе на TP 5.5 (объектно-ориентированным) простые анимации и физическую симуляцию, оконную систему библиотеку.
А потом переписал их на Turbo C++ 1.0. Я вообще не увидел никакого эффекта от С++ , кроме того что он сломал мне весь процесс разработки: элементарные изменения стали компилироваться не пару секунд и пару десятков секунд на сборку всего проекта, а по паре минут сразу, и примерно такое же время выполнения программы; а размер .exe файла программы стал гораздо, в разы больше. То есть: практическая реализация была не практичной, не эффективной. Для меня бесполезной. Программа была та же самая во всех случаях, просто переписанная на двух разных языках — построчный перевод. Потом уже гораздо позже я прочитал изначальную книжку Н. Вирта про реализацию компилятора Паскаля на ISO Pascal. И знаете, как-то практичнее стал его воспринимать. Не говоря уже книжек про Модулу и Оберон.
Ну или компилятор Крешноу сначала на Паскале, потом на Форте. Или реализацию Баранова Паскаля на Форте. В общем лично у меня это начиналось с довольно субъективных вещей. Потом правда, увлёкся ЛОГО, Лиспом и Фортом. С небольшими заходами назад в С++ для работы. И проверкой эффективности и практичности — на своей практике. Иначе сравнивать невозможно.✅ 2022/12/13 00:53, MihalNik #5
Программа была та же самая во всех случаях, просто переписанная на двух разных языках — построчный перевод. Тут много подводных камней. В Паскалевых эффективная модульность, строки, например, содержат размер, а если тупо менять на сишные, ряд алгоритмов просядет. Массивы динамические могут эффективно перестраиваться. Сейчас они, конечно, есть в стандартной библиотеке C++ как std::string и std::vector.Сложность, с которой сталкивается программист при изучении языка — вообще, по большей части относится к описанию реализации, её инфраструктуры, инструментальной технологии, и прочей метатехнологии, чем к какой-то умозрительной сложности языка как синтаксиса (а ещё есть семантика). Не скажите. Сишные "=" и "==" после математического равенства вызывают как непонимание у какой-то части учащихся, так и вполне себе ошибки: по забывчивости и опечатки. Трудоемкость также напрямую влияет на изучение: размещение и удаление в памяти (конструкторы-деструкторы), какие-нибудь громоздкие begin/end в Паскале или итераторах C++, отсутствие автовывода типов, ручное форматирование ввода/вывода как в C и т.п. Медлительность и ошибки не способствуют достижению целей, в т.ч. обучения. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|