Философия дизайна языка программирования

Это не учебник PL/I.
В 1950-х и начале 1960-х годов деловые и научные пользователи программировали для разного компьютерного оборудования, используя разные языки программирования. Бизнес-пользователи переходили от автокодеров через COMTRAN к COBOL, а научные пользователи программировали на Fortran, ALGOL, GEORGE и других. IBM System/360 (анонсированная в 1964 г. и выпущенная в 1966 г.) была разработана как общая машинная архитектура для обеих групп пользователей, заменяющая собой все существующие архитектуры IBM. Точно так же IBM хотела иметь единый язык программирования для всех пользователей. Он надеялся, что Фортран можно будет расширить, включив в него функции, необходимые коммерческим программистам. В октябре 1963 года был сформирован комитет, первоначально состоящий из трех сотрудников IBM из Нью-Йорка и трех членов SHARE, группы научных пользователей IBM, чтобы предложить эти расширения для Fortran. Учитывая ограничения Фортрана, они не смогли этого сделать и приступили к разработке нового языка программирования, в общих чертах основанного на АЛГОЛе, помеченного как NPL. Эта аббревиатура противоречила аббревиатуре Национальной физической лаборатории Великобритании и была ненадолго заменена на MPPL (многоцелевой язык программирования), а в 1965 году — на PL/I (с римской цифрой I). Первое определение появилось в апреле 1964 года.
В «Генеалогии компьютерных языков» Джеймса Хэддока говорится: «В 1964 году IBM разрабатывала свою System/360. Никогда не довольный ALGOL, они хотели иметь собственный диалект в качестве языка реализации системы, но с возможностью обработки приложений в стиле COBOL. Результатом стал PL/1 (они также защитили авторские права на имена от PL/2 до PL/100 на всякий случай).
Они (IBM) на самом деле создали лучший язык программирования. Мы все знаем, что произошло дальше.
С 50-х годов люди создают языки программирования. Можно было бы ожидать, что такой поток творчества в какой-то момент выльется в универсальный формальный язык. Не так. Очень не так.
Количество существующих сегодня языков исчисляется сотнями, и этот список продолжает расти. Эй, есть даже язык, словарный запас которого состоит из острот Арнольда Шварценеггера.
Почему люди продолжают изобретать новые языки?
Правильный инструмент для задачи
Попробуйте вкрутить молотком ослабленный винт, и вы что-нибудь повредите. То же самое касается формальных языков. Не существует единого размера для всех.
Язык программирования, как и естественный язык, допускает широкий, но не безграничный спектр выражений. Некоторые естественные языки, например, будут иметь богатый набор существительных, подробно описывающих набор объектов, имеющих отношение к естественной среде обитания их носителей, в то время как в других их мало или совсем нет, поскольку их носители никогда не нуждались (или, возможно, никогда не встречались). предметы, столь важные для первого.
Навахо, например, имеет десять различных глаголов для переноса, каждый из которых относится к форме и физическим свойствам переносимого объекта, и лишь несколько для выражения психических состояний ("думать", "переносить"). быть сердитым"). Английский язык, с другой стороны, чрезвычайно богат вторым и беден первым.
Еще одна интересная особенность языка навахо — встроенная в него иерархия существ. У навахо люди считаются выше крупных разумных животных, а те выше мелких животных, которые, в свою очередь, считаются выше растений и неодушевленных предметов.
Почему?
Ответ заключается в том, что язык, естественный или формальный, является инструментом, и поэтому он должен быть удобно применим к рассматриваемой проблеме. Если задача переноса так важна для навахо, вполне разумно, что они хотели бы описать ее как можно короче, а не так часто тратить целое предложение. Вы, будучи навахо, ненавидите повторять общеизвестный (вам) факт, что люди выше животных и растений, это просто досадная трата времени.
То же самое касается формальных языков. Формальный язык (программирования) — это инструмент, который позволяет обдумывать проблему и выражать решение этой проблемы. Если ваш словарный запас и языковые метафоры не могут описать проблему, вы все равно можете найти творческие способы самовыражения, однако вы потратите много времени, как англоговорящий человек, пытающийся сделать заказ во французском ресторане.
SANScript, извлеченный урок
В далёком 1999 году мне дали, казалось бы, простую задачу. Меня попросили спроектировать новый язык сценариев, который позволил бы быстро и легко программировать сети с поддержкой сервисов. Для тех из вас, кто не разбирается в мире DPI (Глубокая проверка пакетов), скажу так. Он был задуман как язык сценариев, позволяющий практически любому программисту писать код, который одновременно анализирует миллионы интернет-соединений на уровне приложения, отслеживая его сообщения, события высокого уровня (такие как просмотр веб-страниц или даже содержимое просматриваемой страницы, вызовы VoIP). , обнаружение DDoS-атак и т. д.), и, возможно, модифицировать их в процессе передачи, не получая ничего, кроме необработанных пакетов, проходящих по сети.
Этот «скриптовый» язык быстро стал полноценным языком программирования, который позволял даже самому простому программисту писать такие приложения, не принимая во внимание почти все сложности лежащих в их основе джунглей, основанных на пакетах.
Язык, ласково названный SANscript — Service Aware Networking Scripting Language — позже ставший SML (теперь принадлежащим Cisco), был разработан как DSL, предметно-ориентированный язык, основной целью которого было скрыть сложность пакетного домена от программиста, не ограничивая его возможности самовыражения в создании новых и интересных решений прикладного уровня (также известного как Уровень 7) для реальных проблем.
При создании языка мне пришлось выполнить два, казалось бы, противоречащих друг другу требования (скрыть сложность при сохранении выразительности), что привело к созданию легкого объектно-ориентированного языка с дополнительными структурами и идиомами, позволяющими программисту поверить, что он находится в сериализованной вселенной, в то время как лежащие в основе данные был сильно одновременным (как по времени, так и по данным).
На самом деле это была не многопоточная парадигма программирования, скрывающая сложность лежащих в основе множественных параллельных потоков данных. И это было эффективно и выразительно (программист-новичок после короткого обучения мог легко написать приложение для обнаружения DDoS-атак производственного уровня или приложение для анализа вызовов VoIP, обрабатывающее миллионы потоков TCP/IP за несколько дней).
Как это стало возможным?
Элементы
Каждый язык программирования должен учитывать четыре элемента вычислений, а именно:
- Управление памятью
- параллелизм
- Безопасность
- Представление
Чтобы еще больше усложнить ситуацию, эти базовые элементы обычно переплетаются непредсказуемым образом. Ошибиться при этом равносильно тому, чтобы возиться с четырьмя элементами природы. Вы окажетесь холодным, мокрым и на грани катастрофы. Поскольку мы хотели бы избежать таких катастрофических событий, позвольте мне кратко объяснить каждое из них и их взаимосвязь (если вы чувствуете, что знаете достаточно об этом, не стесняйтесь переходить к следующему разделу).
Каждый язык программирования должен предоставлять программисту возможность ссылаться на память. Различные языки различаются способами, которыми они позволяют выражать ссылки на память. Старые языки, такие как C, отдают память в ваши руки. Возьмите чип и забейте. Если вы запутались, это ваши проблемы. Java, с другой стороны, с ее современным сборщиком мусора, применил противоположный подход, взяв на себя полную ответственность за управление памятью.
Но управление памятью должно учитывать, как язык справляется с параллелизмом. Если это не так, вы можете в конечном итоге выполнять параллельные задачи, которые непреднамеренно обращаются к одной и той же области памяти, повреждая данные программы вне контроля программиста.
Чтобы еще больше усложнить ситуацию, управление памятью также тесно связано с безопасностью. Это потокобезопасность (т. е. не позволять параллельным потокам повреждать переменные друг друга), область видимости (например, защита внутренних членов функций или объектов от повреждения внешним кодом), инкапсуляция объектов и замыкания.
Этот замысловатый танец между тремя языками обычно отличается. При его разработке вы должны сделать выбор, на который, как и на фракталы, сильно влияют ваши начальные условия (основные предположения), приоритеты и, что наиболее важно, цели, которых должен достичь ваш язык.
Это, в свою очередь, расширит ваши языковые метафоры, а именно идиомы, которые программисту придется использовать (и думать) при применении вашего языка.
И есть производительность.
В настоящее время большинство программистов понимают производительность просто как «получить больше облачных процессоров». Но так было не всегда, да и сегодня, по сути, нет.
Увеличение мощности облачного процессора стоит денег, поэтому, исходя из предположения, что вы просто потратите деньги на решение проблемы, вы можете получить право на раннюю кончину, поскольку ваша взлетно-посадочная полоса заканчивается как раз перед тем, как вы достаточно масштабируетесь для следующего вливания долларов.
Следовательно, производительность должна быть принята во внимание. Но только когда это важно.
Крупномасштабная операция, которая должна предоставлять услуги в режиме реального времени миллиардам людей по всему миру, такая как Dropbox или Netflix, должна заботиться о производительности, в то время как веб-приложение, которое обслуживает несколько тысяч запросов в секунду, может не учитывать. Все зависит от вашего варианта использования. Настоящее и будущее.
Однако производительность бесполезна, если код, который вы пишете, полон ошибок, искажений памяти, нарушений параллелизма и тому подобного. Возможно, вы добились большой скорости, но собираетесь проводить выходные за отладкой производственной системы под огромным давлением со стороны как руководства, так и клиентов.
Хотя выбор.
Вот почему разработчики языков очень внимательно относятся к языковым спецификациям, пока не поверят, что им удалось сбалансировать элементы. Ну хотя бы сбалансированные по своим первоначальным целям.
Языковые теократии
Вы когда-нибудь участвовали в дискуссии на тему «какой язык программирования лучше» (это вежливый способ сказать: ссора).
Если вы были в этом бизнесе достаточно долго, вы видели его не раз. Только что окончившие школу программисты, цепляющиеся за язык программирования, только что вышедший из духовки, избивая опытных программистов как устаревшие. Другие, рожденные и выросшие в определенной дисциплине (скажем, ООП), отказываются от нее отказываться, пока проблема кричит используй другой язык.
Почему они не могут просто сойтись?
Потому что никто из них не принимает основную истину. Что разные языки были разработаны с разными целями и должны использоваться соответственно.
Эта проблема может потребовать комбинации языков, поскольку не существует универсального решения. Или просто, что не существует языка, который идеально подходил бы для решения проблемы, и вы должны выбрать ближайший лучший языковой кандидат, принимая тот факт, что время от времени вам придется скрипеть зубами.
Чтобы стать лучшим программистом, нужно научиться делать такие различия еще на этапе проектирования. Как бы ни было сложно выбрать правильный язык или набор языков для каждой области подзадач, гораздо проще исключить языки, которые могут быть крутыми (или модными, или хипстерскими, или каким-то новым термином, который они собираются придумать). за то, что он уникален и намного опережает тех программистов, которые используют языки годичной давности).
Что не использовать
Во-первых, никогда не используйте язык только потому, что он новый и крутой. Это ошибка хипстеров. Если это круто и вам это нравится, проводите свободное время, программируя в нем, пока не почувствуете баланс элементов. Только потом считай.
Во-вторых, обозначьте проблему, которую вы пытаетесь решить, в общих чертах. Является ли это интенсивным процессором? Интенсивный ввод-вывод? Нужен ли масштаб? Нужен ли масштаб сейчас? Выиграет ли он от параллелизма? Полагается ли это на параллелизм? Важна ли производительность? Сейчас важна производительность? Я могу продолжать, но вы поняли, верно?
Получив ответы, вы легко сможете исключить те религиозные аргументы, которые обычно приводят фанатики одного языка. Вы можете разделить проблему на подзадачи, каждая из которых требует своего языка. И вы даже можете планировать этапы реализации в соответствии с тем, что является текущим приоритетом (например, без масштаба) по сравнению с будущим (крупномасштабным), принимая во внимание стоимость «от сюда до туда».
Звучит идеально, правда? Но почти никого не смущает.
Распространенные ошибки
Самая распространенная ошибка при выборе правильного языка или набора f языков — это «проще найти программистов для X». Получить их может быть проще, но каждому придется приложить больше усилий, чтобы превратить свой разум и проблему в неподходящую языковую метафору, которую вы им навязали. Это все равно, что попросить университетского профессора, который всю жизнь писал работы по биологии, быстро сформулировать статью по информатике. Он сделает это, но это займет у него в два раза больше времени, и будет плохо написано.
Вторая наиболее распространенная ошибка — «это язык, с которым я знаком». Какая разница? Выучить новый. Если вы уже знаете несколько, вы быстро адаптируетесь к новому. Ваше время кривой обучения будет незначительным по сравнению со временем, которое вы сэкономите, используя правильный набор идиом и языковых метафор.
Третьим по распространенности является «Это крутой и модный новый язык». Может быть, но было бы глупо тратить свое время (или деньги вашего работодателя) на попытки выучить его, пока вы решаете проблему, когда вы даже не знаете, подходит ли язык (!).
Тем не менее, реальность показала, что большинство программистов используют комбинацию вышеперечисленного с катастрофическими результатами во времени и потерей взлетно-посадочной полосы.
Позвольте мне привести вам лишь несколько примеров.
NodeJS-программистов на JavaScript (а теперь еще и на TypeScript) пруд пруди. Все хотят кодить на JS/TS. Основная причина этого в том, что JS/TS прост в изучении, и вы можете сделать столько ошибок, сколько захотите (в частности, меньше в TS), и язык просто, ну… что-то сделает. Это наихудший выбор для крупномасштабной мультисервисной (или микросервисной) серверной части с несколькими потоками данных и несколькими конечными точками. Тем не менее, многие выбирают этот путь просто из-за ошибки номер один.
Вы спросите, что они могли сделать по-другому? Ну, это зависит от их проблемы. Если им нужна быстрая реализация конечных точек в Интернете, они могут использовать комбинацию JS/TS NodeJS. Если им также нужен бэкэнд, основанный на науке о данных, им было бы разумно использовать (контейнерные) микросервисы Python.
Если им нужна распределенная реализация, которая может выиграть от безошибочной передачи сообщений в качестве базовой модели оркестровки, им, вероятно, следует использовать Go.
Я даже видел, как код Matlab правильно инкапсулируется и используется в крупномасштабных производственных средах, где в то время это был лучший выбор.
RUST можно использовать, поскольку Dropbox решил использовать его, для чрезвычайно крупномасштабных систем, где стоимость его использования (высокая кривая обучения, чрезвычайно недружественные метафоры владения и т. д.) была ниже, чем преимущества безопасности, которые он давал.
Даже C, да эта старая штука, может пригодиться, когда нужно ускорить работу (и не хочется зацикливаться на RUST на несколько месяцев, пока всех не освоишь), и не дай бог ассемблер (или шейдеры как они украли его) для небольших ядер, таких как те, которые используются в программировании для графических процессоров общего назначения.
И список продолжается.
Несколько заключительных слов мудрым
Если вам не нужна производительность, используйте языки, которые облегчают вашу жизнь (сбор мусора), снижают количество ошибок (строгая типизация) и улучшают читаемость кода (принудительная документация).
Если, с другой стороны, вас интересует невероятная производительность, вы можете (если это подходит) использовать графические процессоры с CUDA или писать ядра шейдеров и играть с необработанным управлением памятью до посинения.
Если вы разрабатываете простой динамический веб-сайт с базой данных на бэкэнде и простой логикой, используйте NodeJS и TypeScript (даже не начинайте с JS). Если вы масштабируетесь, вы просто добавите больше узлов, и ваше общедоступное веб-приложение мгновенно начнет получать больше трафика.
Для науки о данных используйте Python из-за его огромной базы библиотек в этой области.
Если вы строите распределенную систему с большими потоками данных, вашим выбором может быть Go (в зависимости от проблемы, поскольку он менее выразительный) или даже RUST (или V, его недавно появившийся конкурент).
Ничто из этого не означает, что вы должны относиться к вышеизложенному как к руководству по эксплуатации, а скорее как к набору примеров.
Как давным-давно сказал Будда, я могу только показать вам путь, которым я шел. Однако вам нужно идти своим путем.
Выбирай с умом.
—
Последние 27 лет автор программировал практически на всех языках и в любых ролях. От низкоуровневого программирования его Sinclair Spectrum 48K на пятнадцати в сборке Z80 до основателя, проектирования стартапами и внедрения крупномасштабных систем, программируемого DPI, обработки естественного языка и многого другого.