У нас есть все необходимые языки - достигнуто совершенство. Или есть?

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

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

Многие люди удивлены и опечалены тем фактом, что JavaScript пожирает мир. Это не потому, что молодые люди слишком глупы или слишком модны для собственного блага, а потому, что многие языки просто не так хороши. Это не неуважение к разработчикам языков - я даже близко не могу быть разработчиком и разработчиком языка. Именно потому, что так сложно создать хороший язык, такого прекрасного языка не существует. Кроме того, требуется время, чтобы получить реальную обратную связь, чтобы разработчики языка могли со временем совершенствоваться. К моменту получения отзывов дизайнер языков практически на пенсии.

Причина, по которой JavaScript так популярен, заключается в том, что большинство языков (1) не могут работать на нескольких устройствах и платформах, (2) недостаточно производительны, (3) не имеют хорошей / универсальной модели параллелизма, (4) раздуты и библиотеки, которые трудно освоить, (5) полезны только в определенных проблемных областях, а (6) слишком разбиты по проблемным областям и не имеют достаточно большого сообщества для поддержки пользователей.

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

Но… можем ли мы создать что-то лучше, чем JavaScript?

Я ищу язык, который я могу использовать на серверах, который я также могу использовать для написания мобильных приложений, настольных приложений, веб-приложений ... и, возможно, просто написать высокопроизводительную многопользовательскую игру. Возможно, это будет слишком много, но я бы предпочел один язык, который я люблю до смерти и чрезвычайно полезный, чем 3–5 языков, которые, на мой взгляд, нормальны.

Некоторые критерии полезного языка:

  • Его можно скомпилировать / перенести для работы на нескольких платформах, включая пользовательские интерфейсы и серверы. Простая транспиляция на другие языки имеет решающее значение, чтобы позволить ей запускаться во многих местах с самого начала.
  • Это позволяет импортировать более 1 версии одной и той же библиотеки / пакета. На работе так много людей застревают на старых версиях библиотек, а затем не могут легко обновить разделы кодовой базы до более новых версий библиотеки. Java, NPM и другие языки не могут легко обновить версии библиотек.
  • Это упрощает написание асинхронного кода - большинству сред выполнения приходится обрабатывать асинхронное событие здесь или там, этого нельзя избежать. Самый простой способ для разработчика избежать ошибок (и необходимости ставить блокировки повсюду) - создать язык, не имеющий (thread) упреждения. Например, в Node.js нет упреждения - сначала запускается весь ваш запланированный код, а затем, когда он завершается, цикл событий переходит к следующему тику. Если бы у Node.js была преимущественная покупка, платформа бы вообще не работала - отсутствие преимущественной покупки - одна из причин, почему это такая мощная платформа. Я считаю, что все среды выполнения JavaScript связаны с этим ограничением, но я не уверен на 100% (кто-нибудь поддержит меня?). С другой стороны, Java имеет преимущественное право потоков, и это затрудняет асинхронное программирование (особенно для написания библиотек для общего использования). Превентивные программные среды просто плохи или, если хотите, морально неправильны *.

Чтобы узнать о некоторых проблемах упреждения на Голанге, см .: https://github.com/golang/proposal/blob/master/design/24543-non-cooperative-preemption.md

  • Для производительности он должен компилироваться в машинный код. Это, конечно, не запрещает языку иметь виртуальную машину и интерпретироваться этой виртуальной машиной вместо компиляции в машинный код (для записи, Java компилируется в байт-код, а затем интерпретируется).
  • Виртуальная машина / интерпретатор могут быть важны, потому что иногда невозможно скомпилировать каждую программу. Вместо этого для написания сценариев мы используем интерпретируемые языки, потому что мы можем запускать программы без необходимости их повторной компиляции для каждой машины.
  • Это структурно-типизированный *, а не номинальный тип. Насколько я могу судить, номинальная типизация дает несколько преимуществ перед структурной типизацией. Структурная типизация делает библиотеки менее раздутыми и более легкими для написания. Языки FP чаще имеют структурную типизацию, чем языки ООП, и я думаю, что структурная типизация однозначно лучше. Структурная типизация особенно полезна для взаимодействия между библиотеками, в противном случае обеим библиотекам необходимо импортировать друг друга.
  • Сделал вывод * типизирует. Типы должны выводиться в максимально возможной степени, цепочки и FP, кажется, помогают в этом, тогда как чистое ООП, похоже, означает множество повторяющихся объявлений.
  • В нем нет правила вне игры, иначе говоря, без пробелов в качестве синтаксиса или значимых пробелов. Синтаксис с пробелами должен быть одной из худших идей во всем языковом дизайне по очевидным причинам. Все дело в тщеславии, которое однажды убьет вас.
  • Он высокопроизводительный, с простой в использовании моделью параллелизма, которая может использовать преимущества современных многоядерных процессоров. Модель цикла событий с асинхронным вводом-выводом, кажется, работает очень хорошо, и это одна из основных причин, почему JavaScript так популярен.
  • Имеет автоматическую сборку мусора и управление памятью.
  • Он избегает стремления к жесткому реальному времени, вместо этого стремится к мягкому-реальному времени. Аппаратное время в реальном времени означает, что используется упреждающая или приоритетная потоковая передача, что значительно усложняет разработку программного обеспечения. Поскольку 99,9% программного обеспечения не требует функций жесткого реального времени, язык с серебряной пулей может избежать этого, и, следовательно, он может избежать упреждения. Программное обеспечение для аппаратного реального времени может включать программное обеспечение систем управления, программное обеспечение для алгоритмической торговли и т. Д.
  • Когда дело доходит до функционального / объектно-ориентированного, в нем есть лучшее из обоих миров, то есть почему бы и то и другое?
  • Он имеет первоклассные функции. Строковые литералы шаблона. Анонимные функции, закрытия и т. Д.
  • Он имеет прочную поддержку на уровне языка для неизменяемости, неизменяемых структур данных и т. Д.
  • У него хорошая модульная система, система упаковки и система управления зависимостями.
  • Это позволяет загружать более одной версии зависимости во время выполнения. Java не допускает этого, что создает ад зависимостей - для данного класса может существовать только один экземпляр, так работает загрузчик классов.
  • Сам язык упрощает написание сторонних библиотек.
  • Он имеет широко доступный и поддерживаемый интерпретатор, поскольку это упрощает реализацию WORA.
  • Имеет легкую десериализацию / сериализацию. JSON, вероятно, здесь ненадолго, но многие типизированные языки делают ввод-вывод слишком сложным.
  • Это технически лучше, так что любой барьер для входа является полностью необходимым, что имеет полезный эффект отгонки неквалифицированных, неопытных или непосвященных программистов.
  • Я не думаю, что перегрузка операторов - это нечто большее, чем тщеславие (поправьте меня, если я ошибаюсь), но я действительно думаю, что перегрузка методов имеет преимущества. Попытки найти два, три или четыре разных названия по существу одного и того же распорядка в лучшем случае могут раздражать. Изучение Голанга напомнило мне, что не во всех языках есть перегрузка методов - это меня огорчает.
  • Это далеко не полный список, и я добавлю его позже.

* Что касается упреждения потоков: в Java это есть, а в Node.js - нет. В настоящее время я изучаю асинхронные среды выполнения, такие как Erlang, чтобы увидеть, могут ли процессы Erlang прерывать друг друга и, следовательно, произвольно изменять порядок выполнения запланированных операций. Если вы знаете об этом, оставьте комментарий. Сборка мусора - одна из задач в большинстве сред выполнения, которая может быть упреждающей.

При асинхронной разработке очень важно, чтобы порядок выполнения был последовательным. В этом случае мы ожидаем, что шаг 2 всегда будет выполняться до шага 3, что бы ни случилось. В средах с упреждающей потоковой передачей иногда существует опасность того, что шаг 3 может быть выполнен до шага 2. В упреждающих средах обычно требуется, чтобы разработчик использовал больше блокировок для предотвращения состояний гонки и т. Д., Что может быть обременительным и подверженным ошибкам. В сообществе Node.js несовместимые API, которые могут запускать обратные вызовы синхронно и асинхронно, известны как освобождение Zalgo или вызов Zalgo.

Вот простой пример неудачного упреждения:

Под * структурной типизацией мы подразумеваем, что при проверке типов рассматриваются только свойства (состав объекта), а не имена классов / типов. Структурная и именная типизация не различаются, когда вы передаете примитивы в качестве аргументов. Но когда вы передаете объект функции, он будет компилироваться на языке со структурной типизацией, если у него есть ожидаемые свойства / методы. Например, с TypeScript:

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

В отличие от Golang, если у нас есть определение метода и передается функция обратного вызова, TypeScript знает типы аргументов для этой функции обратного вызова, поэтому вам не нужно повторно объявлять их.

Возвращаясь к примеру TypeScript сверху, мы видим, что TS подразумевает типизацию:

Как существующие языки не попадают в цель

C++

Я не так много знаю о C ++, потому что никогда не использовал его широко, но я читал об этом бесчисленное количество раз. Насколько мне известно, C ++ очень универсален и мощен, но ему не хватает автоматического управления памятью, что делает его опасным в использовании. Вы можете писать пользовательские интерфейсы для настольных компьютеров, но, насколько мне известно, написать интерфейсы для Android, iOS и Web не так-то просто. Очевидно, причина в том, что люди предпочитают поддерживать другие языки вместо C ++ для работы на своих платформах.

Джава

Java - отличный язык для многих современных программ. Самая большая проблема, с которой я столкнулся с Java, заключается в том, что в ней используется система номинальных / именных типов вместо системы структурных типов - вот почему экосистема Java настолько раздута. Со временем такие инструменты, как GWT для написания веб-приложений, оказались довольно непродуктивными по сравнению с использованием современного JavaScript. Но Java отлично подходит для серверов и Android. По сей день кажется, что Vert.x - лучшая система для написания серверов Java. Кроме того, программы Java не особенно легко компилировать, что привело к тому, что для запуска программ Java необходимы дополнительные инструменты. Это головная боль и основная причина того, почему многие разработчики Java не очень разбираются в командной строке и зависят от инструментов IDE для выполнения каких-либо задач.

Голанг

Голанг был многообещающим, но ему не хватало нескольких ключевых областей. Он не очень поддерживает функциональное программирование, в нем отсутствуют обобщения и наследование, которые являются полезными функциями ООП. Golang действительно набирает основные баллы в отношении своей структурной типизации, доказывая, что система структурных типов может компилироваться очень быстро, однако для этого требуются избыточные объявления типов (см. Выше), которых избегают другие языки со структурной типизацией (например, TypeScript и OCaml). У Golang также есть несколько ограничивающая проблема с циклической зависимостью, когда два файла в разных пакетах не могут зависеть друг от друга (хотя, возможно, все языки компиляции в машинный код имеют это ограничение - похоже, OCaml). Итак, Golang хорош для написания простых высокопроизводительных серверов - но что еще?

Чтобы узнать о некоторых проблемах упреждения на Голанге, см .: https://github.com/golang/proposal/blob/master/design/24543-non-cooperative-preemption.md

OCaml

На мой взгляд, OCaml - самый многообещающий из них. Однако единственное, чего ему не хватает, - это хорошего параллелизма. OCaml был создан до появления современных многоядерных машин и никогда не имел твердой модели параллелизма, кроме порождения другого процесса. Я считаю, что у него есть потоки, но у него также есть GIL. С движением ReasonML, OCaml, возможно, получит больше внимания. Я вижу, что прилагаются большие усилия для улучшения параллелизма OCaml, включая многоядерный OCaml и привязку механизма цикла событий Libuv, используемого Node.js, к OCaml.

Erlang

Erlang - это круто - это высокопроизводительный динамический язык, вроде JavaScript. JavaScript и Erlang - единственные два динамических языка, которые входят в этот список. Однако без TypeScript и Dialyzer они бы это сделали. С Erlang есть пара проблем: 1. Он полезен только для создания масштабируемых бэкэндов - не пользовательских интерфейсов или мобильных приложений и т. Д. 2. Он имеет аннотации типов (которые использует Dialyzer), но эти аннотации типов являются именными не структурный. Использование сторонних библиотек и попытки понять, как они работают без типов, могут стать головной болью. Аннотации типов могут помочь, но я думаю, что они были бы лучше, если бы они были структурными аннотациями. Мы могли бы создавать мобильные приложения с помощью Erlang, как мы это делаем с Swift и Java, но без реальной системы типов это может быть довольно сложно.

F#

F # многообещающий. Это высокопроизводительный клон OCaml, и, поскольку это Microsoft, он может заимствовать множество хороших библиотек из Microsoft / C #. Проблема в том, что F # имеет номинативный тип, потому что он заимствует из существующих библиотек MS. Блин! Тем не менее, хороший инструментарий разработчика с VSCode. В отличие от OCaml, F # использует необязательные значащие пробелы.

C#

C # имеет номинальную типизацию, как Java и F #. Возможно, он лучше, чем Java, но все же имеет номинативную типизацию, что делает его раздутым, как и Java. Однако, как и F #, у него есть хорошие инструменты для разработчиков.

Дротик

Dart чем-то похож на TypeScript. Единственное, чего ему не хватает, - это перегрузки методов. Это не особо функциональный язык, по крайней мере, не в большей степени, чем JavaScript - например, он не поддерживает неизменяемость по умолчанию.

Ржавчина

Наряду с OCaml многообещающим является Rust. Единственное, чего, по-видимому, не хватает в Rust, так это перегрузки методов - функции, которая, на мой взгляд, полезна.

TypeScript ›JavaScript

Помимо OCaml, я считаю, что TypeScript и JavaScript ближе, чем большинство современных языков, к достижению идеала. TypeScript - отличное решение того факта, что JavaScript не типизирован. Одна из основных проблем с JavaScript заключается в том, что вычисления и проблемы с плавающей запятой могут сделать числовые библиотеки немного ненадежными. Хотя у JavaScript есть хорошая модель параллелизма, у модели параллелизма есть несколько подводных камней, которые необходимо устранить - в первую очередь ошибки, возникающие в асинхронно выполняемых блоках. В конечном итоге TypeScript не может решить многие фундаментальные проблемы JavaScript, хотя это, несомненно, лучшая система типов, с которой я когда-либо работал.

Как видим, ни один из рассмотренных мною языков не отвечает всем критериям. Похоже, что самым сложным критерием для удовлетворения является структурная типизация - ему, кажется, удовлетворяют только Golang и OCaml.

Большая угроза

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

Выявление противоречивых / малоизвестных идеалов

  1. Я считаю само собой разумеющимся, что языки со структурной типизацией лучше. Golang предоставляет доказательства того, что они могут быстро компилироваться, а TypeScript показывает нам, насколько прекрасна структурная типизация.
  2. Кажется, что транспиляторы могут заменить компиляторы во многих случаях, но в некоторых случаях люди опасаются дополнительной сложности. Насколько я могу судить, лучше иметь интерпретатор, такой как JVM, который может работать где угодно, а не переносить множество целевых языков. Например, с React Native я опасаюсь писать ReasonML, который транслируется в JavaScript, который затем интерпретируется в собственный код через мост на Android и iOS.
  3. Почему нет компиляторов для интерпретируемых языков и интерпретаторов для компилируемых языков? Похоже, что это позволило бы больше писать один раз в любом месте (WORA). Очевидно, это потребует работы, но также и предусмотрительности.
  4. Инкрементальные компиляторы, такие как tsc --watch для TypeScript, прекрасны, почему в таких языках, как Java, нет инкрементных компиляторов, которые ведут себя одинаково?

Есть куда расти

  1. Время запуска процесса. С такими вещами, как Lambdas в облаке, время запуска процесса для процессов Node.js в 300 мс или около того может быть недопустимым. Чтобы сократить время запуска процесса интерпретируемых языков, мы могли бы создать для них компиляторы. Точно так же, чтобы скомпилированные языки работали повсюду, мы могли бы создать для них интерпретаторы / виртуальные машины.
  2. Использование без вытеснения на уровне языка / времени выполнения. Насколько я могу судить, большинство языков допускают упреждение, которое может произвольно изменять порядок выполнения. Разработчик ожидает, что код будет выполняться в определенном порядке, но при выполнении асинхронной разработки упреждение может это изменить. Разработчик может обрабатывать упреждение вручную с блокировкой (хотя это может быть кропотливым и подверженным ошибкам), но написание библиотек для общего использования в нескольких средах может быть намного сложнее, когда упреждение - это то, о чем нужно беспокоиться. Сборка мусора в Node.js.
  3. Инкрементальная компиляция. Например, с Java вам придется все перекомпилировать. JRebel выполнит горячую загрузку кода, но только после того, как он будет скомпилирован, что остается вашей работой. Как упоминалось выше, я хотел бы видеть больше инструментов инкрементальной компиляции для компилируемых языков. Я использовал только инкрементный компилятор для TypeScript. Это означает, что у вас есть сервер разработки, который хранит скомпилированный код в памяти и отслеживает изменения в файлах.

Заключение

Возможно, квантовые вычисления приведут к этим изменениям. Я готов поспорить, что только большая компания сможет объединить усилия и талант для создания суперязыка.

Помимо квантовых вычислений, моя непроверенная теория состоит в том, что OCaml приближается к идеалу. Я еще не очень много использовал OCaml, но для меня это следующая попытка. Если бы Java была языком со структурной типизацией, я думаю, что этот разговор был бы окончен. Увы, в Java есть многословная система типов с раздутыми библиотеками / экосистемой, потому что система типов является номинальной.

Scala, вероятно, в порядке, но немного медленно компилируется и не так полезен вне серверов. В Rust нет автоматического управления памятью, и я считаю, что в нем также есть номинативная типизация. Лисп не типизирован. Хаскелл тоже непрактичен. F # и C # принадлежат Microsoft, и я стараюсь избегать языков MicroSoft, потому что я не работаю в Windows - хотя кажется, что F # является клоном Microsoft OCaml точно так же, как C # был клоном Java для MS.

Есть ли язык, который мне не хватает? Какой язык, по вашему мнению, самый мощный и универсальный? Можете ли вы представить себе язык программирования лучше всех существующих?