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

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

Теперь мы переформулируем изменения предыдущего раздела как давление эволюции языка, обсуждая: факторы, которые поддерживают языки программирования (раздел 3.1), силы, которые ведут к развитию языка (раздел 3.2), и случаи, когда языки стали практически вымершими из-за отсутствия эволюции (раздел 3.3).

3.1 Факторы, которые поддерживают языки программирования живыми

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

Устаревшее программное обеспечение. Количество программного обеспечения в мире увеличивается день ото дня. Новые системы (и модули внутри систем) создаются гораздо быстрее, чем они удаляются. Существующий код, выполняющий ценные бизнес-функции, необходимо обновлять и обновлять, чтобы обеспечить дополнительную функциональность или интегрировать с другими системами. Замена системы оптом является важным решением и дорогостоящей для больших систем. Необходимость стремиться к существующим успешным системам, используя тот язык, на котором они были изначально написаны, является сильной силой в поддержании языков. Несмотря на то, что COBOL часто воспринимается как мертвый язык, совсем недавно, в 2009 году, по оценкам, в активном использовании находились миллиарды строк кода [http://skeptics.stackexchange.com/questions/5114/did-cobol-have-250-billion-linesof-code-and-1-million-programmers-as-late-as-2 [sic]], и некоторые из них наверняка останутся, даже если они не будут широко рекламироваться.

Сообщество. Энтузиазм и поддержка определенного языка программирования часто поощряются, если вокруг него есть активное и ободряющее сообщество. Это может быть полезно для поощрения новичков или для поддержки программистов в их повседневных задачах, предоставляя советы по техническим проблемам по мере их появления. Языковые сообщества, как и любая социальная группа, имеют тенденцию отражать параметры дизайна языка, который они обсуждают; некоторые считаются формальными и академическими, а другие более приземленными. Например, в сообществе Ruby есть высказывание, которое имеет репутацию поддерживающего и полезного: «Мац [создатель языка] хорош и поэтому мы хороши»[https://en.wiktionary.org/wiki/MINASWAN]. Напротив, сообщество, которое выросло вокруг Scala, воспринимается как гораздо более академически сфокусированное, склонное к большему математическому мышлению и языку, а иногда воспринимаемое как менее приветливое к людям из числа «общих разработчиков». В своей (несколько полемической) заметке ScalaDays 2013 [https://www.youtube.com/watch?v=DBu6zmrZ 50, особенно с 21 мин]. Род Джонсон рассказывает о сообществе Scala как о том, где «кажется, что есть немало людей, которые не слишком сосредоточены на решении проблем реального мира» и где появляются некоторые участники, имеющие мнение, что «невежество должно быть наказано».

Легкость начала работы. Для того чтобы овладеть новым языком, полезно, если начинающим программистам (новичкам или просто новичкам в рассматриваемом языке) можно легко и быстро начать. Скорость, с которой новый программист может писать и запускать свою первую программу, зависит от многих вещей: простота проектирования языка, ясность учебных пособий, количество, которое нужно изучить, прежде чем начать, и поддержка инструментария (включая полезные сообщения об ошибках, предмет, принятый близко к сердцу такими языками, как Elm). Это, в свою очередь, может повлиять на долговечность языка, поскольку зависит от постоянного притока новых программистов.

Обитаемость. В своей книге «Образцы программного обеспечения» [12] Габриэль описывает характеристику обитаемости: «Обитаемость делает жилье пригодным для жизни, это и есть «дом». И это то, что мы хотим от программного обеспечения - чтобы разработчики чувствовали себя как дома, они могут взять в руки любой элемент, не задумываясь о том, где он находится». Если в языке широко используются идиомы, и проекты, разработанные на этом языке, обычно закладываются в соответствии с привычной структурой, тогда программистам будет легче чувствовать себя как дома. Языки, которые продвигают «один способ сделать что-то», могут помочь создать такую обстановку. Обитаемость может также исходить из наличия общей инструментальной экосистемы [Обратите внимание, что другие виды использования «экосистемы» в этом документе относятся к экосистеме языков, конкурируют за нишу программирования, но «инструментальная экосистема» относится к набору инструментов и доступна для поддержки программирования на данном языке - мы ранее отметили, что это улучшило пригодность данного языка, ведя себя как симбиот]. Например, если мы загружаем проект Java и обнаруживаем, что он структурирован как проект Maven [https://maven.apache.org/], то легко найти источник, тесты, зависимости, конфигурацию сборки и т.д., если мы знакомы с другими проектами Maven.

Точно так же в современных проектах Ruby можно ожидать определенной структуры и использования Bundler [http://bundler.io/2] для выполнения аналогичной роли.

Такие инструменты часто являются лишь фактическими стандартами в результате широкого внедрения, но последовательная компоновка проекта, процесс сборки и т.д., предоставляемые стандартными инструментами, снижают когнитивную нагрузку для разработчика и, в свою очередь, могут заставить программиста почувствовать больше себя дома при работе на заданном языке, особенно когда он приступает к работе над новым проектом [14].

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

Обильный запас библиотек порождает еще более обильный запас библиотек. Однако мы отмечаем, что недавние тенденции полагаться на сторонние библиотеки даже для самых простых процедур могут привести к проблемам. Например, удаление широко используемой библиотеки «left-pad» из менеджера пакетов JavaScript «Npm» вызвало много хаоса [http://www.theregister.co.uk/2016/03/23/npm хаос левой панели].

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

Java, например, является очень популярным языком, который очень продуктивен для многих групп разработчиков. Это связано не только с дизайном самого языка - некоторые могут утверждать, что это на самом деле, несмотря на это, поскольку Java часто рассматривается как относительно неискушенный с точки зрения языковых возможностей. Это также возможно, возможно, в значительной степени - благодаря поставке хороших инструментов и библиотек, доступных для разработчиков Java. К счастью, мы слышали истории от многих коммерческих разработчиков, которые активно выбирают работу на Java, а не на более сложном языке, из-за наличия мощных инструментов, ориентированных на Java, таких как IntelliJ IDEA [https://www.jetbrains.com/idea/], с их широкой поддержкой автоматического рефакторинга и преобразование программы, в отличие от сравнительного отсутствия поддержки инструментов для более богатых языков. Этот пробел в поддержке инструментов может даже помешать освоению языков, нацеленных на JVM, таких как Scala, что было решено в недавней поддержке IntelliJ IDEA для Scala и Java 9.

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

Поддержка изменений. Кто-то может подумать, что гибкие методы предпочтут языки с динамической типизацией, и, действительно, отчасти их популярность заключается в том, что они позволяют разработчику делать что-то, не беспокоясь о множестве «пустяков», связанных с более строгими и многословными языками. Но для статически типизированных языков существует противодействие при работе на больших системах. Agile-методы способствуют внедрению изменений, при этом кодовые базы развиваются через непрерывную последовательность обновлений и рефакторингов [10]. Эти действия по преобразованию кодовой базы легче выполнять, когда они поддерживаются с помощью инструментов, и инструменты рефакторинга работают лучше, когда доступна информация о типе [40]. Однако это не улица с односторонним движением. Сложность системы типов Scala по сравнению с Java усложняет создание эффективных инструментов автоматического рефакторинга. Кроме того, дополнительная сложность компилятора означает, что по сравнению с Java время компиляции и сборки относительно велико. Это может привести к разочарованию, когда разработчики хотят часто вносить небольшие изменения.

Рабочий процесс, который включает частые небольшие изменения в работающей системе, требует средств, на которые разработчики могут положиться, чтобы снизить риск и обеспечить устойчивый прогресс. Практика, обычно используемая гибкими командами, - это разработка через тестирование (TDD) [3]. Ошибочно утверждать, что определенные языки явно поддерживают TDD, но на языке, таком как Java или C#, с хорошим инструментарием, мы можем многое сделать, работая в стиле TDD, потому что инструменты могут генерировать для нас много кода реализации на основе теста и типа - хотя на самом деле это всего лишь механизация повседневной ручной работы, в игре нет реального интеллектуального синтеза [Это в отличие от таких методов, как тестирование на основе свойств, которые разумно синтезируют тесты, используя гарантии системы типов и спецификации свойств, определенные программистом [7]]. В динамических языках нам нужны тесты еще больше, потому что у нас меньше уверенности в безопасности и правильности нашей программы со стороны система типов. К счастью, некоторые разновидности тестов - например, тесты с использованием фиктивных объектов [25] - могут быть более удобными для написания на динамических языках, поскольку нам не нужно вводить абстракции, чтобы «убедить» систему типов в том, что конкретный тест double [https://martinfowler.com/bliki/TestDouble.html] может использоваться в совместимый с типом способом в качестве замены реальной зависимости, учитывая позднюю привязку, которая характерна для таких языков.

Поставка талантов. При создании новой системы выбор языка - это не только техническое решение, но и экономическое. У нас есть разработчики, необходимые для создания этой системы? Если нам нужно нанять больше или заменить одного сотрудника на один день, сколько будет стоить нанять человека с соответствующими навыками? Знание некоторых языков легко найти, в то время как другие - специализированные ниши. На момент написания статьи на рынке труда есть хорошие программисты, которые знают Java, C#, JavaScript и т.д.

Разработчиков Haskell, Rust или Erlang сравнительно немного. Этот дефицит предложения относительно спроса, естественно, приводит к высоким ценам.

Высокая производительность. Некоторые языки (в частности, C и C++) не умрут в ближайшем будущем, потому что они настолько производительны. Мы возвращаемся к долголетию C в разд. 5.1 и рассуждать о будущем C и C++ в разделе 6.1.

Целью разработки относительно нового языка Rust является достижение C-подобной производительности без ущерба для безопасности памяти с помощью инноваций систем типов. Чтобы достичь высокой производительности, Rust, в частности, стремится обеспечить статические гарантии безопасности памяти, чтобы избежать накладных расходов на проверки во время выполнения.

Важная ниша. Некоторые языки особенно хорошо решают важный класс проблем. Ada едва используется, если мы смотрим глобально, но она очень жива (особенно подмножество SPARK в Ada) для создания высоконадежного программного обеспечения [28]. То же самое верно для Фортрана, но в отношении научных вычислений.

Оба языка имеют сравнительно новые стандарты (Ada 2012 и Fortran 2008).

3.2. Стимулы для эволюции

Технический прогресс. Достижения в области технологии делают принципиально возможными новые приложения, а языки адаптируются, чтобы сделать их возможными - и выполнимыми для создания - на практике. Ранняя цель Java состояла в том, чтобы стать языком Интернета, а массовое использование Интернета в качестве платформы для приложений привело к поддержке и росту таких языков, как JavaScript и PHP. Тот факт, что JavaScript является единственным языком программирования, обычно поддерживаемым всеми веб-браузерами, сделал его стандартом де-факто для интерфейсных веб-разработчиков. Появление iPhone и его нативных приложений привело к всплеску разработки в Objective-C, когда программисты создавали приложения, а позже Apple создала язык Swift, чтобы обеспечить лучший опыт для разработчиков на iOS. Технология многоядерных процессоров привела к тому, что параллелизм поддерживается, хотя и фрагментарно, на многих других языках, чем это было бы в противном случае.

Надежность и безопасность. Как обсуждено в разд. 2.3, многие языки теперь управляются, так что основные свойства корректности проверяются во время выполнения, и таким образом программист может меньше заботиться о распределении и освобождении памяти. Это устраняет большие классы уязвимостей безопасности, связанных с неправильным доступом к памяти. Обычно синтаксис и семантика языка развиваются в поддержку аргументации программы: через ключевые слова для заданных программистом утверждений и контрактов (особенно заметно в эволюции Ada благодаря подмножеству SPARK [28] [Наша точка зрения заключается в том, что некоторые языки эволюционировали для поддержки контрактов. Контракты также получили первоклассную поддержку с момента создания некоторых языков, например Eiffel]); через более продвинутые системы типов, такие как универсальные (чтобы избежать небезопасных приведений), типы для управления владением памятью (например, в Rust) и зависимые типы для кодирования более богатых свойств (все больше доступных в функциональных языках); путем обновления языковых спецификаций с более строгой семантикой для операций, которые ранее были определены только неофициально [Одним из примеров является C ++ 11, добавляющий семантику параллелизма для слабых моделей памяти; Трудность этого проиллюстрирована его нежелательным поведением «из воздуха» (OOTA)]; и путем добавления средств для программистов, чтобы указать намерение разработки программного обеспечения (например, возможность аннотировать метод переопределения с помощью @Override в Java или с помощью переопределения, спецификатор в C++, в случае ошибки ошибка статически переопределяется).

Наряду с эволюцией языка, ведущей к повышению надежности и безопасности, существует также понятие подмножеств языка, которые способствуют более дисциплинированному программированию или предоставляют больше возможностей для инструментов анализа, включая предложенное безопасное подмножество C++[http://www.stroustrup.com/resource-model.pdf], ECMAScript «строгий» режим для JavaScript и, опять же, SPARK-подмножество Ada.

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

Еще одним источником вдохновения является отсутствие побочных эффектов в функциональном стиле, которое используется, например, Google MapReduce [8]. Если процессор, выполняющий одну часть «карты», выходит из строя, тогда другой процессор может просто повторить работу. Это было бы намного сложнее с побочными эффектами и распределенным откатом. Интересным проектом в этом направлении был Murray et al. [31] Движок облачного исполнения CIEL (и связанный с ним язык сценариев Skywriting), где вычислительная идемпотентность была основным принципом проектирования.

Конкуренция между языками. Некоторые языки развиваются посредством конкуренции. Например, многие функции C# находились под влиянием Java; в свою очередь, поддержка лямбд в Java 8, по-видимому, находилась под влиянием аналогичных возможностей в C#, и C++ был дополнен поддержкой функций более высокого порядка примерно в то же время. В многоядерном мире существует явная конкуренция между CUDA и OpenCL: CUDA лидирует по передовым функциям, которые могут поддерживать графические процессоры NVIDIA, и OpenCL постепенно внедряет успешные функции, которые также могут быть реализованы у поставщиков графических процессоров. Эволюция, основанная на конкуренции, демонстрирует для пользователей ценность наличия нескольких языков, занимающих одну и ту же нишу.

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

Известные примеры включают в себя расширения Microsoft для C и C ++, разработку Go, Rust и Swift от Google, Mozilla и Apple, соответственно, и Hack как расширения PHP от Facebook [http://hacklang.org/]. Сообщества с открытым исходным кодом также создали влиятельные языковые расширения - возможно, большинство в частности, различные расширения GNU для C и C++.

3.3 Вымирание из-за неэволюции

Языки исчезают, когда они больше не используются, но мы должны отделить слова «больше не используются для новых проектов» (например, COBOL) от «(вероятно) систем, использующих их, не существует» (например, Алгол 60). (Конечно, общественная поддержка исторических языков и систем означает, что даже эти определения являются подозрительными.)

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

а) революционная замена: концепции языка были новаторскими, но его использование в более широкой экосистеме было менее привлекательным - другие языки, которые включали его особенности, теперь вытеснили его; и

б) потеря пригодности: язык широко использовался во многих крупных проектах, но возникли сомнения в его сохраняющейся экологической пригодности. Алгол 60 и, возможно, Smalltalk хорошо соответствуют первому из этих критериев, а Фортран, Лисп, С и, возможно, КОБОЛ - второму.

Языки в последней категории могут избежать исчезновения путем развития. Это наиболее заметно в случае для Fortran, но C также подходит для этой схемы, как и C++: новые возможности были добавлены в языки в последующих версиях. Точно так же Лисп развился с Common Lisp, Racket и Clojure как современные формы. Ключевой вопрос здесь - обратная совместимость: старые программы должны продолжать работать в более новых версиях языка с минимальными текстовыми изменениями. Популярный метод удаления старых функций, которые считаются вредными для будущих версий языка, состоит в том, чтобы исключать их - отмечать их для будущего удаления и разрешать цепочки инструментов предупреждать об их использовании. Этот метод используется в стандартах ISO для C и Fortran (самые последние стандарты в 2011 и 2008 годах соответственно); Лиспские диалекты развивались более разрозненно, но все еще в значительной степени обратно совместимы.

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

Закрывая этот раздел, мы отмечаем, что Fortran (впервые стандартизированный в 1958 году, последний стандарт в 2008 году и Fortran 2015 [sic], активно занимающийся стандартизацией) кажется практически бессмертным для крупномасштабного числового кода, такого как прогнозирование погоды и моделирование движения планет. Как ни странно, он в значительной степени борется с C++ из-за возможностей, которые C++ предоставляет для написания кода, и которые другие не могут легко понять или которые содержит незначительные ошибки. Одним из примеров этого является то, что Fortran в значительной степени запрещает использование псевдонимов, что ограничивает гибкость языка для программирования общего назначения, но уменьшает возможности для ошибок, связанных с программированием псевдонимов, и помогает компиляторам создавать эффективный код.

В отличие от этого, Алгол, несмотря на то, что он, возможно, является самым влиятельным языком всех времен, фактически вымер - попытка перейти от Алгола 60 к Алголу 68 не оказалась эффективной для удержания его территории. Трудно определить точную причину: другие языки, разработанные в то время, когда стандарт Algol был фактически заморожен в ожидании Algol 68, отсутствие поддержки (или даже вера сообщества) в отдельную компиляцию, по умолчанию вызов по имени означает, что любой разумное обновление не будет обратно совместимым и т. д.

Здесь есть интересное сравнение: «Вы бы предпочли быть бессмертным или посмертно хвалить за распространение ваших генов по всему миру?» Цитата из Джо Армстронга также кажется уместной здесь [41]:

Люди продолжают спрашивать меня: «Что будет с Эрлангом? Это будет популярный язык? Я не знаю. Я думаю, что он уже был очень влиятельным. Это может закончиться как у Smalltalk. Я думаю, что Smalltalk очень, очень влиятельный и любимый группой энтузиастов, но никогда не получал широкого распространения.



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

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

Оцените

Отзывы

     2019/11/29 15:01, MihalNik          # 

Целью разработки относительно нового языка Rust является достижение C-подобной производительности без ущерба для безопасности памяти с помощью инноваций систем типов.

Последнее словосочетание жутковато.

     2019/12/03 22:36, Автор сайта          # 

Заменил «с помощью инноваций систем типов» → «с помощью нововведений в системе типов».

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

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

Авторизация

Регистрация

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

Карта сайта


Содержание

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

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

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

Компилятор

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




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

2019/12/08 17:48 ••• Noname
Почему обречён язык Форт

2019/12/05 23:29 ••• Автор сайта
Слоны в комнате

2019/12/03 22:45 ••• Автор сайта
Следующие 7000 языков программирования: заключение

2019/12/03 22:36 ••• Автор сайта
Наблюдаемая эволюция языка программирования

2019/11/30 20:55 ••• Сергей
Каким должен быть язык программирования?

2019/11/09 21:27 ••• kt
Программирование без программистов — это медицина без врачей

2019/11/07 10:58 ••• kt
Признаки устаревшего языка

2019/10/28 23:55 ••• Автор сайта
Типы в инженерных задачах

2019/10/15 16:32 ••• kt
Модификация исполняемого кода как способ реализации массивов с изменяемыми границами

2019/10/07 14:15 ••• Автор сайта
О наименовании проекта и языка программирования

2019/09/19 15:23 ••• kt
Некошерный «goto»

2019/09/13 16:38 ••• Автор сайта
Программирование исчезнет. Будет дрессировка нейронных сетей