<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Haskell</title>
    <description/>
    <link>https://tproger.ru/tag/haskell</link>
    <atom:link href="https://tproger.ru/tag/haskell/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Thu, 01 Oct 2026 20:36:18 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Haskell</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Мое видение универсального языка программирования</title>
      <link>https://tproger.ru/articles/moe-videnie-universalnogo-yazyka-programmirovaniya</link>
      <comments>https://tproger.ru/articles/moe-videnie-universalnogo-yazyka-programmirovaniya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Миша Разаков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/moe-videnie-universalnogo-yazyka-programmirovaniya</guid>
      <description><![CDATA[<p>Рассмотрим теоретическое создание универсального языка программирования. Плавно перейдем к идее языка-конструктора. Вас ждет погружение в модульную архитетуру, лямбда-исчисления, байткод, расширяемое программирование

</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/moe-videnie-universalnogo-yazyka-programmirovaniya">Мое видение универсального языка программирования</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 30 Aug 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Предисловие</h2><p>В этой статье будет много моих выводов, гипотез и идей. Это моя первая статья, поэтому буду рад услышать аргументированную критику. В статье часто используется псевдокод, чей синтаксис напоминает смесь C#, C++ и Python.</p><h2>Немного о терминологии</h2><p>Много времени я программирую на C#, и в этом языке существуют некоторые термины, которые могут ввести в заблуждение программистов на C++:</p><ul><li>Под словом коллекция я подразумеваю любой набор элементов: список, массив, стек, куча, итератор, множество и т.п.</li><li>Говоря список, я подразумеваю динамический массив</li></ul><h2>Язык программирования – инструмент</h2><p>Языки можно поделить на две большие группы: предметно-ориентированные языки (aka DSL) и языки общего назначения (ЯОП). DSLи точно можно назвать инструментами, так как они по определению решают конкретную задачу. С языками общего назначения все несколько сложнее. ЯОПы имеют техническую возможность реализовать любой алгоритм, то есть полны по Тьюрингу. Но это не значит, что для каждой задачи будет эффективно использовать язык X, ведь язык Y может подходить для решения каких-то задач лучше.</p><p>Под словом “эффективно” я подразумеваю, что программистам будет морально приятно писать код на языке для решения конкретной задачи, а также, что код будет написан за минимальное количество времени.</p><p>Рассмотрим банальный пример. C# лучше использовать для написания бекенда сайтов, чем Си. Используя C#, вы делаете вклад в возможность поддержки проекта, уменьшаете порог входа в проект, пишете платформо-независимый код. Си лучше использовать в задачах, где важна производительность или низкоуровневый доступ к ресурсам. Например при написании runtime библиотеки языка, игрового движка или кода под микроконтроллеры с небольшими ресурсами.</p><p>Также некоторые языки имеют хорошую поддержку сообщества в каких-то специфических областях. Например C# и C++ – доминируют в создании игр, Python и R – преобладают в data science, JavaScript и PHP – в создании сайтов и т.п. В силу своих реализаций не все ЯОП могут решать задачи друг друга эффективно. Интерпретируемый язык никак не может быть также быстр в исполнении, как статически компилируемый, но может выигрывать в скорости кодирования.</p><p>В итоге приходим к выводу, что язык программирования, даже общего назначения, – это просто инструмент, который умеет выполнять задачи эффективно только из своей области.</p><h2>А в чем проблема?</h2><p>Проблемы начинаются тогда, когда мы обнаружим, что используемый язык программирования по какой-то причине не умеет решать специфическую задачу своей области эффективно. Например C# не всегда бывает лаконичен: иногда не хватает функциональной парадигмы или нет возможностей сократить лишний синтаксис при композиции. В таких случаях придется смириться и реализовывать функционал через костыли, например, создавая лишние интерфейсы, нужные чтобы обойти запреты языка.</p><p>Python не станет быстрым, C++ не будет хорошо подходить для написания скриптов, а Fortran не получит современный синтаксический сахар.</p><p>Если вы программировали на C# и Unity, возможно, вы писали какие-нибудь математические функции или методы-расширения. Я с разочарованием для себя обнаружил, что Unity использует C# 9-той версии, в которой нет обобщенной арифметики (ее введут в C# 11). Скорее всего, красиво и лаконично реализовать какой-нибудь метод SquareMagnitude не получится: придется писать одну и ту же реализацию для каждого типа по отдельности (дженерики не помогут: нет обобщающего интерфейса).</p><p>Помимо этих реализаций, было бы неплохо иметь реализацию с типами: byte, sbyte, ushort, uint, ulong, decimal. Хорошо, что метод SquareMagnitude достаточно маленький, но если бы это был большой метод, код стал бы еще хуже. Принцип DRY (don’t repeat yourself) плачет в сторонке.</p><h2>Extensible Programming</h2><p>Решением видится расширяемое программирование (<a href="https://en.wikipedia.org/wiki/Extensible\\_programming">Extensible Programming</a>) – это парадигма программирования, когда расширяемость возводится в абсолют. Возможности языка не фиксированы, а могут быть расширены. Программист пишет не только расширяемые модульные программы, но и программирует на расширяемом языке. Программист имеет возможность изменять синтаксис языка, исполняемый движок (компилятор/интерпретатор/JIT’а и т.п.), среду исполнения (environment) – все то, что мы называем Toolchain’ом языка. Эта парадигма позволяет кастомизировать язык программирования, добавлять eDSL (embedded DSL, встроенные в язык. Как пример: LINQ в C#). Зачастую кастомизация даже представляется как одно из основных средств программирования. Например, на расширяемом языке есть возможность добавлять новые синтаксические конструкции, изменять AST, байткод, контролировать процесс компиляции, изменять среду исполнения и т.п. Самые известные из расширяемых языков: Ruby, Lisp, Lua.</p><p>Предлагаю посмотреть <a href="https://habr.com/ru/articles/571182/">эту статью</a> на хабре, в которой демонстрируется пример написания простенького генератора кода для элегантных лямбд на Lua. Это пример расширяемого синтаксиса языка. Ruby тоже имеет расширяемый синтаксис, например для него написана библиотека <a href="https://rubygems.org/gems/superators19">superators19</a>, позволяющая <a href="https://stackoverflow.com/questions/11874579/define-custom-ruby-operator">добавлять новые операторы</a> в язык.</p><p>Конечно, расширяемое программирование нужно не всем, но эта парадигма – мощный инструмент для сложных систем, которым важны какие-то аспекты языка, которые не идут из коробки. Не всегда в языке не хватает каких-нибудь мелочей по типу лаконичности синтаксиса, но и таких важных механизмов, как верифицируемости корректности исполнения, формального распараллеливания и т.п. Используя расширения, можно добавлять такие механизмы, которые не были реализованы ранее или улучшать уже имеющиеся имплементации. Подобные фичи – достаточно сложные и реализовать их на уровне, например, препроцессора, невозможно. Приходится использовать более глубокое внедрение в используемый язык.</p><h2>Моя модель идеального языка программирования</h2><p>Идея заключается в возможности построить микро-язык на модульной архитектуре, который будет содержать в себе ядро и несколько дополнительных модулей для его комфортного расширения. Сам микро-язык будет не очень полезен, но на его основе можно будет сделать диалекты, которые уже можно будет считать полноценными языками общего назначения. При надобности, программист сможет отключать, модифицировать или добавлять модули в язык. Так язык превратится в набор модулей, буквально конструктор. Чтобы внести изменения в язык, будет достаточно понимать его работу в целом, а также понимать работу только тех модулей, с которыми предстоит работать.</p><p>Например, представим что JavaScript сделан по данной идее, и хочется добавить в язык рантайм-проверку типов (на минуту забудем про существование TypeScript). Можно было бы написать модуль, который добавляет немного нового синтаксиса в объявление переменных, обрабатывает AST (т.е. использует модуль парсера) и добавляет в каждое использование переменной/поля/т.п. проверку на ее тип.</p><p>Псевдо-код реализации модуля мог бы выглядеть как-то так:</p><h2>Абстрактные детали реализации</h2><p>Итак, какие задачи языка мы имеем на данный момент? ЯП должен:</p><ul><li>иметь возможность расширять свои frontend, middle-end, backend</li><li>иметь простой API для расширения</li><li>поддерживать большинство парадигм (функциональное, декларативное программирование)</li></ul><p>Сначала разберемся с первыми двумя задачами. Как уже было сказано ранее, архитектура языка будет модульной. Значит нужно просто для каждого модуля предусмотреть возможность интеграции с другими модулями. При этом желательно предоставить интуитивный API для доступа к различным частям модуля. Кроме того, каждому модулю не плохо иметь свое краткое описание работы, ведь для написания своих модулей придется иметь понимание всех модулей, которые должны использоваться.</p><p>Рассмотрим пример с созданием простейшего фронтенда. Простой фронтенд может быть разделен на небольших модуля: лексер и парсер. Допустим, программист захотел добавить препроцессор перед лексером. Какой удобный API можно предоставить? По моему мнению, хорошо подходят события. Пусть лексер и парсер имеют события OnPreprocess и OnPostprocess. Таким образом, например, программист будет иметь возможность обработки последовательности лексем уже после ее генерации. Также программист сможет изменять AST уже после его генерации.</p><p>Но как добавлять новые правила построения дерева или лексемизации текста? Можно создать методы по типу AddLexerRule, AddParserRule, которые будут принимать в себя экземпляры классов, имплементирующих интерфейсы ILexerNodeBuilder и IParserNodeBuilder. Но стоит учесть, что в большинстве языков грамматика не самая простая, а значит, что парсер желательно делать по мощности LR(k), LALR(k) или ALL(*). Возможно стоит сделать небольшой DSL для этих модулей, так как это сильно упростит поддержку языка в дальнейшем (или использовать уже готовый, такой как ANTLR). DSL поможет программистам не погружаться в технические подробности реализации и достаточно просто добавлять изменения в грамматику.</p><p>Но как же коллизии имен? Конечно, они будут возникать гораздо чаще, поэтому стоит предусмотреть некое подобие namespace’ов. Например, если в начале файла написано use parser.math, то парсер активирует дополнение к текущей грамматике, добавляющее математические операторы.</p><h2>Модули</h2><p>Что, если язык окажется сложным? Например, парсер будет очень тяжелым, язык будет использовать супероптимизаторы или просто модуль окажется проприетарным. В таком случае желательно иметь возможность разбросать модули по разным физическим местам. Например, чтобы разместить супероптимизатор в облаке, парсер поставить на компьютер с бОльшим количеством оперативной памяти, а доступ к проприетарному модулю давать только через web-request’ы (чтобы нельзя было получить исходный код).</p><p>Хорошим решением оказывается представлять модули как web-сервисы. Это делает модули гораздо более гибкими. Плюсы модулей как веб-сервисов:</p><ul><li>можно расположить в разных физических местах</li><li>IDE будет способна контролировать все процессы языка – особенно удобно, если нужно глубокое понимание языка, например просмотр промежуточных представлений</li><li>при возникновении потребности можно легко использовать утиную типизацию над модулями</li><li>можно использовать вообще любые языки программирования и технологии</li><li>модули гораздо проще воспринимать как логически разделенные части – повышается гибкость системы и уменьшается ее связность</li></ul><p>Буквально получается язык программирования на микросервисах. Возможно, сначала покажется, что такой подход сильно замедлит процесс компиляции, но, скорее всего, это скажется достаточно незначительно. На моем слабеньком ноутбуке запросы к локальным веб-серверам почти никогда не превышали 10мс, а значит чтобы добиться задержки хотя бы в половину секунды, придется сделать 50 запросов. Стоит учитывать, что общение между модулями происходит не очень часто, поэтому, думаю, беспокоиться о сильном замедлении не стоит.</p><p>Стоит заметить, что я предлагаю использовать модули как веб-сервисы исключительно на этапе компиляции. Конечно, модули, которые используются в рантайме должны быть сделаны эффективно, чтобы не создавать серьезного замедления исполнения.</p><h2>Парадигмы</h2><p>Язык программирования должен поддерживать большинство существующих парадигм. Но основными являются императивное и функциональное программирование. С возможностью написания модулей, из них строятся все остальные парадигмы.</p><p>Рассмотрим поддержку функционального программирования. Как известно, сердце функциональной парадигмы – это лямбда-исчисления. Думаю, что как ядро языка – отлично подойдут чистые нетипизированные лямбда-исчисления. Уже с помощью модулей можно дополнять λ-исчисления до system F, λ-исчислений с зависимыми типами или других исчислений. То есть достраивать ядро до нужной мощности и уже на имеющейся основе строить язык. Также фишка такого подхода в том, что, используя синтаксический сахар и монады, в язык можно добавить лаконичное императивное программирование. Но стоит быть аккуратным с чистыми функциями. Нужно учесть, что функция не будет чистой, если внутри себя она использует платформозависимые функции или вызывает внешние нечистые функции.</p><p>Но язык, ядром которого будут лямбда-исчисления, скорее всего, не будет иметь возможности быть оптимально скомпилирован под популярные архитектуры (x86-64, ARM, RISC-V). Для языков, которым важна производительность, размер программы, потребление памяти и т.п., предлагаю ввести иное ядро. Это ядро должно быть универсальным для любого императивного языка программирования, а также должно иметь возможность глубоких оптимизаций.</p><p>У ядра на лямбда-исчислениях есть еще некоторые проблемы: я не придумал, как устранить зависимости между модулями в такой реализации, поэтому, возможно, будет хорошо эмулировать работу исчислений на байткоде императивного языка. Будет написан еще один модуль, превращающий промежуточное представление лямбда-исчислений в байткод (не скупимся на количество IR’ов ради гибкости и удобства).</p><h2>Байткод императивного языка</h2><p>Итак, ядро императивного языка программирования, конечно, будет представлять собой байткод. Сначала я думал использовать какую-нибудь видоизмененную машину Тьюринга, но решил, что она слишком сильно оторвана от текущих процессоров и нет смысла даже пытаться адаптировать ее под современные системы. Самая сложная задача проектирования байткода – он должен подходить под любой императивный язык, но при этом иметь возможность оптимизировать выходной код настолько, насколько это возможно. Сразу же хочется добавить поддержку LLVM для статической компиляции, а также компиляцию в dotnet bytecode, java bytecode для поддержки JIT компиляции. Если тщательно формализовать байткод, то даже есть надежда написать супероптимизатор на middle-end уровне.</p><p>Сделать один универсальный байткод, конечно, невозможно. Снова прибегнем к модульной архитектуре. Пусть инструкция байткода представляет собой такой тип:</p><p>То есть кортеж из типа байткодовой инструкции и трех списков. Изначально байткод будет иметь только один тип инструкции: call. Массивы содержат только <a href="https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BB%D0%B5%D0%B3%D0%B0%D1%82_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)">делегаты</a> на функции (указатель + сигнатура + доп. информация). Байткод буквально представляет собой <a href="https://ru.wikipedia.org/wiki/%D0%A8%D0%B8%D1%82%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4">шитый код</a> (threaded code) – массив вызовов функций (но не обязательно чистых). Массивы различаются лишь порядком применения операций. В массив [0] добавляются делегаты, которые должны быть исполнены перед делегатами массивов [1] и [2]. Грубо говоря получаем делегаты, которые обязательно должны быть исполнены перед или после исполнения других делегатов. Зачем это нужно, рассмотрим позже. Стоит отметить, что вместо таких массивов подошел бы также multiset&lt;(float, delegate)&gt; делегатов (отсортированный список по приоритету вызовов).</p><p>Существует несколько видов байткода: стековый, регистровый и в SSA форме. Для упрощения будем использовать стековый байткод, так как он легко переводится в регистровую и SSA форму. Да и сам по себе, гораздо более абстрактный. Регистровый байткод имеет привязку к количеству регистров, а байткод должен не зависеть от архитектурных особенностей. SSA форма – удобный вариант, но он более сложный. При разработке модулей он может сильно помешать, так как придется “жанглировать” передачами регистров, строить CFG.</p><h2>Продолжение байткода</h2><p>Представим ситуацию. Мы уже имеем минималистичный язык программирования: уже существуют локальные переменные, метки, вызовы внешних функций и другой примитивный функционал. Сейчас типизация динамическая, но хочется внедрить рантайм проверки типов, как в примере с JavaScript. Реализация примерно такая же: добавляем новое правило в лексер, обходим узлы AST с использованием переменной, внедряем assert как ноду перед использованием переменной. Но вдруг программист обнаруживает, что ему не хватает структур в языке. Обычных базовых структур, которые просто хранят значения, в которых нет даже методов (их можно добавить, но без методов проще). Итак, программист написал модуль с поддержкой подобного синтаксиса:</p><p>который превращается в подобный байткод:</p><p>Главная проблема в том, что ни на этапе AST, ни на этапе байткода инструкции LoadField и SetField не получается распознать, как использование переменной. Конечно, на уровне AST невозможно распознать такую конструкцию, так как создаются новые типы нод. А вот с байткодом есть возможность подружиться, если продумать типы инструкций. Можно сказать, что тип инструкции байткода – это ее тег. Первая мысль – делать строгие инструкции, например SetLocal, LoadGlobal, LoadMethodReference и другие. Но проблема таких типов в том, что они слишком конкретны.</p><p>Постараемся развить абстракции. Оставим только инструкции Set и Get, которые будут оперировать интерфейсами (точнее классами/структурами, имплементирующими их) ISettable и IGettable. Чтобы вызвать эти методы, нужны экземпляры классов. Для этого перед каждой установкой или чтением значения будем класть на стек ссылку-обертку на значение. Ссылка, в данном контексте, это не всегда указатель на ячейку памяти. Это абстрактный указатель на само значение. Класс, имплементирующий ISettable и IGettable. Ссылка может быть регистром, реальным указателем или быть ячейкой словаря (map’ы). Рассмотрим пример:</p><p>Как видно, логика написана достаточно абстрактно, нет никакой конкретики. Как работает ISettable – неясно, да и не нужно. Наша задача – написать относительно простой, максимально обобщенный код.</p><p>Инструкции байткода также могут возвращать значения. Первая мысль реализации: как и в обычных функциях, возвращаем значение через return. Но в байткоде встречаются такие инструкции, как swap. Они принимают два значения и возвращают два значения. Поэтому, для удобства, стоит возвращать значения через аргументы-ссылки, предназначенные только для записи (в C# – ключевое слово out). Например:</p><p>Возвращаясь к проблеме типизации и структур, теперь модуль типизации может вставлять проверки перед каждой инструкций типа Set/Get, а модуль структур будет генерировать код установки и получения значений именно через Set и Get. Тут нам и пригодятся три списка делегатов в инструкции. Проверка типов обязана исполнятся перед тем, как основная инструкция будет выполнена, поэтому проверку стоит поместить в нулевой список делегатов. Также плюсом будет то, что модули работают на уровне байткода, а не AST. Это позволит без боли менять фронтенд языка и не переписывать большинство модулей.</p><p>Теперь, понимая смысл трех списков, можем дать им названия: pre-ops, main-ops, post-ops – делегаты, исполняемые, соответственно, до, во время и после совершения основной операции.</p><p>В результате реализации байткода, придерживаясь таких идей, удастся убрать зависимость между модулями. Модуль типизации может ничего не знать о модуле структур. Аналогично модуль сборщика мусора ничего не будет знать о модуле синтаксического сахара.</p><h2>Перфоманс байткода</h2><p>Скорее всего, сложилось ощущение, что при компиляции такого байткода получится очень неэффективный ассемблерный код. В принципе, так и есть. При компиляции без некоторых оптимизаций итоговый код не уберет абстракции, не уберет виртуальные вызовы методов и в принципе не заметит некоторые паттерны, которые можно было бы упростить. Поэтому, если временем компиляции можно немного пожертвовать в пользу перфоманса, нужно в первую очередь (то есть перед всеми остальными оптимизациями) инлайнить все методы шитого кода. Уже после инлайнинга этих методов производить оптимизации. Снова рассмотрим пример:</p><p>Как видно, при достаточно глубоком инлайнинге (2-3 шага) можно сильно упростить код. Уже после инлайнинга, оптимизатор паттернов может сильно упростить некоторые под-коды. Например можно научить оптимизатор распознавать обращение к классу VariableWrapper</p><p>и заменять обращения на их быстрые аналоги (как было показано в примере, переменная была размещена в регистре rax, а не в словаре). Аналогично можно применить и другие оптимизации. А те инструкции, для которых оптимизатор не смог сгенерировать оптимизированный код, будут исполняться как обычно, в лоб. Если написано обращение к словарю, то просто вызываем переданную функцию. Такой подход позволяет писать как высокопроизводительные коды, благодаря оптимизаторам, так и универсальные, если оптимизаторы, например, не умеют компилировать код под какую-то платформу.</p><p>Конечно, качественная компиляция такого байткода – долгий процесс. Поэтому можно контролировать уровни оптимизации. Отключив инлайнинг, свертку констант и т.п., можно добиться достаточно глупой, но быстрой компиляции. Код просто будет представлять собой вызовы функций. И наоборот, задав глубину инлайнинга, включив свертку констант, аллоцируя структуры в регистрах или на стеке, векторизуя обработку данных, можно достичь высокой скорости исполнения.</p><p>Обсуждаемый байткод легко перевести в байткод виртуальной машины dotnet или JVM. Также не сложно перевести в LLVM IR, чтобы уже этот фреймворк применял сильные оптимизации.</p><h2>Рассматриваем узкие места</h2><h2>Дебаггинг</h2><p>Возможно отладка станет проблемой при фиксе ошибок компиляции байткода. Не плохой идеей кажется отслеживание, какие инструкции байткода были сгенерированы какими модулями, чтобы потом искать ошибку именно в них. Реализовать это можно достаточно просто через инкапсуляцию логики в классе коллекции байткода. Аналогичное замечание относится и к AST, ассемблерному листингу и другим промежуточным представлениям. Также хорошо добавить логирование почти каждого действия при изменении промежуточных представлений. Для совершенствования процесса отладки можно написать простенький интерпретатор этого байткода.</p><h2>Зависимости между модулями</h2><p>В худшем случае, количество зависимостей будет O(n^2), где n – количество модулей. Байткод специально был спроектирован так, чтобы зависимость между модулями была минимальной. Но даже с этим условием все еще стоит стараться делать модули абстрактными и подумать заранее о написании других модулях, которые смогут расширять его исходные возможности. Когда будет написан модуль с типизацией, нужно подумать, как улучшить имеющиеся абстракции для потенциальных будущих модулей с ее улучшением (вспомним <a href="https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%BA%D1%83%D0%B1">лямбда-куб</a>).</p><h2>Общее API модулей</h2><p>Также мне нравится идея с веб-сервисами тем, что модулям придется общаться через json’ы. Это сразу заставит продумать API создаваемого модуля. Мучаться с бинарной совместимостью не придется, так как обмен информацией достаточно высокоуровневый и многие проблемы решают такие протоколы как, например, http.</p><h2>Заключение</h2><p>Не думаю, что когда-нибудь будет создан один универсальный язык программирования, который подойдет для решения всех задач. Но, скорее всего, именно удобное окружение и продуманная архитектура поможет создавать языки как конструктор для нужных задач. Нельзя допустить роста множества DSL’ей, где один язык решает одну проблему, чтобы не пришлось каждый день учить новый инструмент, но база модулей помогла бы иметь гораздо более полезный эффект. Не хватает высокоуровневого программирования в системном языке? Подключите модуль с zero-cost абстракциями. Не хватает функционального программирования в ООП языке? Подключите модуль с синтаксическим сахаром, оператором |&gt;, ленивыми вычислениями и <a href="https://en.wikipedia.org/wiki/Uniform_function_call_syntax">unified calls</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Если бы я хотел стать разработчиком на Rust в 2025, с чего бы я начал?</title>
      <link>https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-</link>
      <comments>https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-</guid>
      <description><![CDATA[<p>Гайд по Rust. Показываем, что нужно знать, чтобы научиться языку программирования Раст. Рассматриваем пошаговую инструкцию и практические примеры ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-">Если бы я хотел стать разработчиком на Rust в 2025, с чего бы я начал?</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Операционные системы]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[WebAssembly]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Пет-проект]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Apr 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Rust — язык общего назначения, который ориентирован на высокую производительность и безопасное управление памятью. На нем можно писать софт практически для любого направления: CLI-утилиты, высоконагруженные сервера, десктопные приложения, мобильные приложения (с некоторыми оговорками), игры и игровые движки, прошивки для микроконтроллеров, операционные системы, драйвера и даже браузерные приложения (через компиляцию в WebAssembly).</p><p>В рейтинге языков программирования TIOBE Rust занимает 14 место. Для сравнения — в прошлом марте он был на 17 позиции. Вместе с экспертами <a href="https://solvery.io/?utm_source=article&amp;utm_medium=partner&amp;utm_term=tproger&amp;utm_content=rust_2025&amp;utm_campaign=main_page">Solvery</a> <a href="https://solvery.io/ru/mentor/bondiano?utm_source=article&amp;utm_medium=partner&amp;utm_term=tproger&amp;utm_content=rust_2025&amp;utm_campaign=vasiliy_kuzenkov">Василием Кузенковым</a>, full-stack разработчиком в Web3 стартапе, и <a href="https://solvery.io/ru/mentor/belyaev_dmitry?utm_source=article&amp;utm_medium=partner&amp;utm_term=tproger&amp;utm_content=rust_2025&amp;utm_campaign=belyaev_dmitriy">Дмитрием Беляевым</a>, Rust developer в Wildberries, разбираемся, как стать разрабом на Расте в 2025 году.</p><h2>Немного об особенностях</h2><p>Если вы до этого программировали на ООП языках, то вам, возможно, бросалось в глаза отсутствие привычных классов. Вместо них здесь алгебраические типы данных и трейты для решения expression problem. Их механизм куда больше похож на typeclasses из Haskell, что также будет для вас новой концепцией при построении крупных приложений, и нужно будет перестраивать мышление.</p><blockquote>Также когда стартуешь, немного непривычно работать с move-семантикой, RAII, borrow checker’ом и лайф-таймами. Но компилятор сыпет довольно подробными ошибками, которые можно легко поправить, если разобраться.</blockquote><p>А еще у Rust очень строгая типизация и очень мощная система типов, а сами типы построены так, чтобы предоставлять некоторые гарантии программисту. Например, ссылки в Rust всегда ссылаются на объект, гарантировано существующий в памяти, а стандартные строки содержат только валидный UTF-8. При этом в подавляющем большинстве случаев тип необязательно указывать явно, компилятор способен выводить типы, анализируя контекст функции целиком. Кроме того, Rust следует идеологии «компилируется, значит, работает». От логических ошибок, конечно, Раст не спасёт, но тем не менее очень большой пласт багов можно отловить на этапе кодинга. Да, это сложно, но лучше помучиться при разработке, чем потом разбираться почему упал прод.</p><p>Ещё одна отличительная фишка — абстракции с нулевой стоимостью. Rust позволяет писать высокоуровневый и понятный код, который при этом будет иметь ту же производительность, что и более низкоуровневый оптимизированный вручную вариант.</p><blockquote>Хороший пример здесь — итерация по различным коллекциям. Многие языки позволяют использовать итераторы с их абстракциями вроде map или filter. Только такой код, как правило, будет в несколько раз медленнее, чем если то же самое переписать на циклы. Компилятор Rust способен развернуть такой итератор в обычные циклы сам, и производительность будет сравнима, а порой даже лучше, так как программисты часто при написании низкоуровневого кода заставляют процессор делать много лишних вычислений.</blockquote><h2>Сложно ли переходить на Rust</h2><p>У Rust достаточно нетривиальная кривая входа, и без понимания некоторых важных принципов сложно написать код, который хотя бы будет компилироваться. Это относится не только к полным новичкам в программировании, но и к людям, которые уже владеют другим языком.</p><blockquote>На рынке фактически все вакансии требуют уже какого-то опыта в разработке. И сложность перехода сильно разнится от вашего текущего стека. Для Go-программиста переход будет средне-сложным, а язык, возможно, покажется перегруженным. Для С++ — менее сложным, но язык покажется местами ограничивающим. Я переходил на него с JS/TS’а и столкнулся со множеством низкоуровневых концепций, о которых раньше мог не задумываться. Но если у вас есть опыт в системном программировании — переходить будет в разы проще.</blockquote><p>Однако Rust прививает программисту очень много хороших привычек, которые меняют подход к написанию кода и на других языках. Это однозначно хороший выбор в качестве первого языка, но при условии, что у вас есть достаточно времени и терпения на освоение.</p><blockquote>В моей практике менторства много успешных кейсов перехода на Rust с самых разных языков, но проще всего он даётся тем, кто раньше писал на современном C++ и уже понимает такие концепции, как move-семантика и RAII. Много привычного здесь обнаружат и те, кто писал на функциональных языках (Haskell  или OCaml). Но в целом, для остальных тоже нет никаких проблем, даже если вы совсем новичок.</blockquote><h2>С чего начать изучать Rust</h2><p>Вместе со стартом в изучении языка с The Rust Book — бесплатной официальной книгой по Rust’у — стоит углубить свои знания в более низкоуровневых вещах: в чем разница стека и кучи, что такое разметка памяти и адресация в памяти, как работает процессор, подходы и проблемы многопоточного программирования, плюс почитать про операционные системы и сети. Если вы решили осознанно применять Rust, оно вам пригодится.</p><blockquote>Начать можно даже имея только самую базу в программировании: переменные, ветвления, циклы, функции. Крайне желательно разобраться в устройстве памяти, что такое стек и куча, а так же какие области памяти бывают помимо них.</blockquote><p>Вот примерный список того, что нужно учить на старте:</p><ul><li><b>Переменные.</b> Они по умолчанию неизменяемые (let x). Чтобы x стал изменяемым, нужно указать это явно через let mut. Константы (const) и статические переменные (static) вам будут нужны редко, они имеют свои особенности.</li><li><b>Типы.</b> Стоит разобраться с составными типами, такими как массивы и кортежи, а также с пользовательскими объявляемыми конструкциями struct (тип-произведение) и enum (тип-сумма). Также типы данных в Расте есть стандартные: bool, i32, u64 и прочие числовые, но строки могут удивить, так как их видов сильно больше.</li><li><b>Match.</b> Нужно понять такую вещь, как pattern-matching, познакомиться с оператором match, а также осознать, что pattern-matching применяется не только в нём, а везде, где возможно объявление переменных.</li></ul><p>Таким образом, вы можете сразу проверить что-то и присвоить результат в переменную:</p><p>Здесь мы сразу проверили и присвоили:</p><p>Тут нам потребовалась дополнительная изменяемая переменная.</p><h2>Что такое Cargo</h2><p>Cargo —  это консольная утилита, которая устанавливается вместе с компилятором языка. Она служит одновременно для управления зависимостями, сборки проекта и запуска тестов. Плюс для Cargo есть расширения, например, в поставке по умолчанию уже есть форматтер и линтер clippy.</p><p>Cargo рассчитан на то, что вы будете запускать его через терминал, самые полезные команды это:</p><ul><li>cargo new — создаёт новый шаблонный проект в указанной папке;</li><li>cargo build — собирает проект;</li><li>cargo run — собирает проект и запускает получившийся исполняемый файл;</li><li>cargo check — dry-run сборки, делает все проверки компилятора, но ничего не собирает, что заметно быстрее полноценной сборки;</li><li>cargo test — собирает проект со всеми тестами и запускает их;</li><li>cargo fmt — форматирует проект в общепринятый стиль кода;</li><li>cargo clippy — запускает линтер, очень полезно, можно подсказать более оптимальные варианты кода, найти некоторые потенциальные логические ошибки;</li><li>cargo install — устанавливает пакет, содержащий исполняемые файлы;</li><li>cargo clean — очищает все артефакты сборки.</li></ul><p>Cargo позволяет описать структуру, настройки (профили сборки, описание крейта) и зависимости вашего крейта или даже монорепозитория (с помощью workspace). Кроме непосредственного запуска через терминал, многие вещи могут запускаться через средства интеграции в IDE, такие как rust-analyzer для VSCode.</p><h2>Как работать с ownership, borrowing и lifetimes?</h2><p>Для многих новичков системы владения (ownership), заимствования (borrowing) и времен жизни (lifetimes) выливаются в борьбу с компилятором раста. Эти механизмы нужны в первую очередь для безопасности памяти без сборщика мусора. Общее правило владения такое:</p><p><i>Каждое значение в Rust имеет переменную, которая называется его <b>владельцем</b>. В каждый момент времени может быть только <b>один владелец</b>. Когда владелец выходит из области видимости, значение уничтожается.</i></p><p>Когда значение перемещается (передается другой переменной или функции), владение переходит, и исходная переменная становится недействительной.</p><p>Для типов, реализующих трейт Copy (например, целые числа, булевы значения), значения копируются автоматически:</p><p>Для более сложных типов нужно использовать метод clone(), чтобы создать глубокую копию. В местах программы, где производительность не так важна — использование clone() не возбраняется.</p><p>Если же у вас критичный к производительности кусок кода, то вам также понадобится заимствоватние и лайфтаймы.</p><p>Заимствование позволяет использовать значение без получения владения через ссылки (&amp; и &amp;mut). Для мутабельных ссылок `&amp;mut` есть дополнительные ограничения: в каждый момент времени может существовать только одна изменяемая ссылка на значение.</p><p>Нельзя иметь изменяемую ссылку, если уже есть неизменяемая ссылка на то же значение.</p><p>Времена жизни же гарантируют, что ссылки действительны на протяжении всего времени их использования.</p><p>Аннотация 'a указывает, что возвращаемая ссылка будет жить как минимум столько же, сколько кратчайшая из входных ссылок.</p><p>Общие советы по заимствованию здесь такие:</p><ul><li>Используйте ссылки, когда не нужно владение.</li><li>Возвращайте значения из функций для передачи владения обратно.</li><li>Используйте клонирование для создания новых экземпляров (с пониманием стоимости).</li><li>Используйте типы с трейтом Copy, когда это возможно.</li><li>Еще полезно не забывать о контейнерах. Rc&lt;T&gt; позволяет иметь несколько владельцев одного значения через счетчик ссылок, а RefCell&lt;T&gt; обеспечивает проверку правил заимствования в рантайме.</li></ul><h2>О структурах, перечислениях, модулях и функциях, замыканиях, итераторах</h2><p>Структуры (structs) в Rust позволяют создавать пользовательские типы данных, объединяющие связанные значения и выступающие типом произведения.</p><p>Перечисления (enum) позволяют определить тип, перечисляя все возможные варианты значений.</p><p>Перечисления в Rust являются типами суммы, поскольку значение может быть одним из вариантов:</p><p>Тип Shape представляет объединение (сумму) всех возможных вариантов. И к этому есть мощное сопоставление с образцом для работы с ADT:</p><p>Модули позволяют организовать код и контролировать видимость элементов. Определяются они через синтаксис mod &lt;name&gt; {} и могут быть вложенны друг в друга:</p><p>Также модули могут быть организованы в различных файлах (имя файла в таком случае будет именем модуля):</p><p>Импорт модуля осуществляется через ключевое слово use:</p><p>Функции в Rust определяются через ключевое слово fn:</p><p>Можно делать функции высшего порядка и передавать в них, как обычные, так и анонимные функции:</p><p>Еще одним элементом функционального программирования в Rust выступает итератор:</p><p>Здесь мы создаем собственный итератор Counter, реализуя стандартный трейт Iterator. Он используется для многих встроенных коллекций, таких как Vec или HashMap. Этот трейт особенно удобен из-за различных функциональных комбинаторов из стандартной библиотеки:</p><p>Трейт Iterator предоставляет множество методов адаптеров, таких как enumerate, filter или map, которые возвращают новый итератор. Методы адаптеров ленивые, они не запускают итерацию. Также есть методы исполнители, которые итерируют пока не закончатся значения, например, collect, fold или count.</p><p>Большинство коллекций (и ссылки на них) реализуют трейт IntoIterator (способность кастоваться в Iterator). Также IntoIterator автоматически реализуется для любого Iterator (ничего не стоящий каст сам в себя). Цикл for в Rust работает только с объектами, реализующими IntoIterator.</p><h2>Как обрабатывать ошибки в Rust</h2><p>В Rust принято разделять ошибки на 2 вида: паники и результаты операций:</p><ul><li><b>Паники</b> используются для непредвиденных ситуаций и ошибок программиста, например, деление целочисленного типа на 0 или выход за границу массива. И хотя паники можно отловить, стандартное и рекомендуемое поведение при них — программа упадёт, будет напечатан стектрейс.</li><li><b>Результаты операций</b> выражаются типом Result&lt;T, E&gt;, который является перечислением из двух вариантов — Ok(T) и Err(E). Такой подход гарантирует, что все ошибки строго типизированы, а без обработки ошибки невозможно извлечь результат операции.</li></ul><p>Option&lt;T&gt; используется для представления значения, которое может отсутствовать:</p><p>Result&lt;T, E&gt; используется для операций, которые могут завершиться ошибкой:</p><p>Для удобства проброса ошибок наверх существует оператор ?, который пишется после любого выражения, возвращающего Result, и возвращает Ok вариант. В случае Err варианта будет выход из функции с возвращением ошибки.</p><p>Обычно все ошибки описываются в перечислениях, а для уменьшения шаблонного кода используются крейты вроде <a href="https://google.github.io/comprehensive-rust/error-handling/thiserror.html">thiserror</a>.</p><h2>Про асинхронное программирование</h2><p>Асинхронное программирование в Rust строится вокруг трейта Future— его реализуют для типов, представляющих значение, которое будет доступно в будущем.</p><p>Также в Rust есть синтаксис async/await. Ключевым словом async могут быть отмечены функции и блоки кода — они будут возвращать анонимный тип, реализующий Future. Async-блоки также могут захватывать окружение подобно замыканиям. Внутри async-блоков и функций возможно использовать ключевое слово await на любом выражении, возвращающем Future или IntoFuture (способность кастоваться к Future). В отличие от других языков с подобным синтаксисом, await записывается через точку после выражения, что очень удобно для построения цепочек вычислений.</p><p>Для исполнения асинхронного кода необходим рантайм, но стандартная библиотека такого рантайма не предоставляет, поэтому приходится использовать сторонние библиотеки. Самым популярным рантаймом является библиотека tokio.</p><blockquote>В асинхронное программирование на Rust я рекомендую приходить уже после углубленного изучения языка, первых пет-проектов и небольшой работы с многопоточным кодом. Хотя синтаксис и общие правила работы с асинхронным кодом покажутся знакомыми тем, кто знает JS или C#, из-за более низкоуровневой природы языка работать с ним немного сложнее.</blockquote><h2>Что еще должен знать новичок в Rust</h2><p>Вот примерный список:</p><ul><li>Очень желательно погрузиться в устройство памяти процесса, узнать, что помимо стека и кучи существуют и другие области (например, исполняемый машинный код так же отражён на память, а static-переменные хранятся не в стеке и не в куче, а в своей собственной области). Неплохо было бы и разобраться с тем, что у типов помимо размера есть выравнивание. Что в Rust бывают ZST (zero size type) — типы, размер которых честный 0, и DST (dynamic size type) — типы, размер которых неизвестен во время компиляции.</li><li>Обязательно разобраться, как Rust освобождает память, не используя сборщик мусора. Почитать, что такое RAII. Понять, как работает трейт Drop.</li><li>Избавится от стереотипов о Rust. Rust — не самый сложный язык, как только вы поймёте, как он работает. Плюс платят за Rust, как правило, больше, чем на аналогичных позициях на других языках.</li><li>Оставить свои привычки из других языков (за исключением разве что Haskell/OCaml). Здесь не получится писать, как на C++/Java/Go и т.д. Привыкайте к хорошему и станете лучше, чем были до освоения Rust.</li></ul><h2>Что изучать, если есть вся база: чек-лист</h2><ul><li>Макросы и метапрограммирование</li><li>Unsafe Rust для низкоуровневого контроля</li><li>Интеграция с C/C++ через FFI</li><li>Разработка встраиваемых систем</li><li>WebAssembly</li><li><a href="https://doc.rust-lang.org/nomicon/">rustnomicon</a></li><li>Undefined Behavior</li><li>unsafe-код</li></ul><h2>Тренды на 2025 год</h2><p>На Rust’е пишут все. Более полный список можно посмотреть тут: <a href="https://github.com/rust-unofficial/awesome-rust">https://github.com/rust-unofficial/awesome-rust</a></p><p>Вот несколько топовых фреймворков и библиотек:</p><ul><li>serde — фреймворк для сериализации/десериализации</li><li>tokio, futures — для асинхронного программирования</li><li>clap — парсер аргументов командной строки</li><li>anyhow, thiserror — удобная работа с ошибками</li><li>chrono — работа с датой и временем</li><li>dashmap — многопоточная hashmap</li><li>bytes — эффективная работа с сырыми байтами</li><li>log, tracing — для логирования</li><li>reqwest — для http запросов</li><li>axum — для http сервера и REST api</li><li>mockall, test-case — упростит написание тестов</li><li>bevy — игры</li><li>clippy — линтер</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Хакеры уходят от C++ — вирусы на Haskell и Delphi труднее анализировать</title>
      <link>https://tproger.ru/news/--hakery-uhodyat-ot-c-----virusy-na-haskell-i-delphi-trudnee-analizirovat</link>
      <comments>https://tproger.ru/news/--hakery-uhodyat-ot-c-----virusy-na-haskell-i-delphi-trudnee-analizirovat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/--hakery-uhodyat-ot-c-----virusy-na-haskell-i-delphi-trudnee-analizirovat</guid>
      <description><![CDATA[<p>Хакеры пишут вирусы на Haskell, Delphi и Rust — такие вредоносы труднее анализировать и обнаружить традиционным антивирусам</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/--hakery-uhodyat-ot-c-----virusy-na-haskell-i-delphi-trudnee-analizirovat">Хакеры уходят от C++ — вирусы на Haskell и Delphi труднее анализировать</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Lisp]]></category>
      <category><![CDATA[Аналитика]]></category>
      <category><![CDATA[Хакеры]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 31 Mar 2025 12:43:59 GMT</pubDate>
      <content:encoded><![CDATA[<p>Исследователи из университетов Греции и Нидерландов <a href="https://www.theregister.com/2025/03/29/malware_obscure_languages/">обратили</a> внимание: всё больше вредоносов создаются не на привычных C или C++, а на менее популярных языках — Haskell, Delphi, Rust и даже Lisp.</p><p>Это не мода, а стратегия: так вирусы проще скрываются от антивирусов и сложнее поддаются анализу.</p><p>Когда антивирус ищет угрозы, он часто использует статический анализ — проверку кода без запуска. Но если вирус написан на редком языке, инструменты просто не понимают, что с ним делать. Они не видят знакомых шаблонов и не могут оценить поведение — вирус остаётся незамеченным.</p><h2>Чем реже язык — тем выше шансы обойти защиту</h2><p>Классика вроде C и C++ всё ещё в ходу, но всё чаще встречаются варианты: Python, Go, Delphi, Rust. Например, группировка APT29 использовала Python, Go и даже смесь из Delphi и C#. Hive и Akira перешли на Rust, а BlackByte — на Go.</p><p>Такие языки часто компилируются нестандартно: код получается сложным и запутанным. Даже простой вирус на Go может выглядеть, как серьёзная программа с кучей функций. Это затрудняет проверку и делает автоматический анализ почти бесполезным.</p><h2>Не только язык, но и компилятор играет роль</h2><p>Авторы изучили почти 400 тыс вредоносных файлов и выяснили: важен не только язык, но и компилятор.</p><p>Инструменты вроде Delphi или Tiny C создают такой код, который антивирусы практически не узнают. Причина простая — слишком мало обучающих примеров, чтобы построить надёжные сигнатуры.</p><p>Даже если вирус делает то же самое, что на C++, его версия на Haskell может пройти мимо всех систем защиты — просто потому, что она выглядит иначе и не совпадает с известными шаблонами.</p><h2>Вредоносы становятся всё разнообразнее</h2><p>С каждым годом злоумышленники используют всё более необычные языки и технологии. Это усложняет жизнь аналитикам: чем реже встречается технология, тем сложнее её анализировать вручную, а автоматические системы почти бессильны.</p><p>Исследователи призывают не игнорировать эту тенденцию: чем больше экзотики в арсенале хакеров, тем выше риск, что очередной вирус окажется невидимкой. Нужно учиться замечать и такие угрозы — иначе даже простой вирус сможет проскочить под носом у самой продвинутой защиты.</p>]]></content:encoded>
    </item>
    <item>
      <title>Что такое рекурсия и как с ней работать: разбираемся на примерах</title>
      <link>https://tproger.ru/articles/chto-takoe-rekursiya-i-kak-s-nej-rabotat</link>
      <comments>https://tproger.ru/articles/chto-takoe-rekursiya-i-kak-s-nej-rabotat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/chto-takoe-rekursiya-i-kak-s-nej-rabotat</guid>
      <description><![CDATA[<p>Что такое рекурсия - Основы работы с рекурсией - Tproger
description: Что такое рекурсия. Показываем, как правильно работать с рекурсией и когда она нужна. Рассматриваем пошаговую инструкцию и примеры ✔ Tproger
</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/chto-takoe-rekursiya-i-kak-s-nej-rabotat">Что такое рекурсия и как с ней работать: разбираемся на примерах</a>»</p>]]></description>
      <category><![CDATA[Рекурсия и динамика]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Scala]]></category>
      <category><![CDATA[Графы]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 05 Mar 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рекурсия — это мощный инструмент в программировании, который позволяет решать задачи, разбивая их на более простые подзадачи. В статье рассмотрим базовые понятия рекурсии, её принципы, примеры использования, а также типичные проблемы, с которыми можно столкнуться при написании кода.</p><h2>Рекурсия: что это такое и зачем нужна</h2><p>Рекурсия — функция, которая вызывает саму себя. Ее базовое применение — разбить большую задачу на несколько мелких, что делает код проще и понятнее, или когда нужно повторить какое-то действие несколько раз. Второй случай похож на русскую матрешку — вы достаете куклу за куклой, пока не дойдете до самой маленькой.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-02-27/1ac7a0f3-4cad-4616-8226-f5814dbc266e.jpg" alt="" /></figure><p>А теперь давайте разберемся на простом примере из математики: представим, что у нас есть функция F, которая выполняет определенное действие. В нашем случае — вычисляет факториал числа (это самый классический пример). Внутри этой функции F в качестве одного из значений используем вызов этой же функции F, но с уменьшенным аргументом. Так функция будет вызывать саму себя до тех пор, пока не достигнет базового случая (о нем расскажем ниже), после чего начнет возвращать результаты.</p><p>В виде кода это выглядит так:</p><p>Здесь функция F вычисляет факториал числа n, умножая его на факториал n-1, и так до тех пор, пока не дойдёт до 1, после чего начнёт возвращать результаты вверх по цепочке вызовов. Этот пример как раз о том, что нужно выполнить несколько одинаковых действий.</p><p>А вот пример из жизни: вы листаете ленту в известной социальной сети, а новые посты обновляются автоматически — это рекурсия в действии.</p><h2>Принципы работы рекурсии</h2><p>Рекурсия строится на двух ключевых принципах: базовый случай и рекурсивный вызов. Они позволяют функции вызывать саму себя, пока она не завершится.</p><h3>Базовый случай</h3><p>Базовый случай рекурсии — условие, при котором рекурсивный вызов прекращается. Без него функция будет вызывать саму себя бесконечно, а значит, стек переполнится.</p><p>Один из самых простых примеров — факториал, о котором мы писали выше. Другой — возведение числа в степень до тех пор, пока значение не станет равно искомому.</p><p>Так это выглядит в виде кода:</p><p>То есть, базовый случай — если exponent == 0, то возвращаем 1.</p><h3>Рекурсивный вызов</h3><p>Рекурсивный вызов — шаг, на котором функция вызывает саму себя, но с измененными параметрами, чтобы задача стала проще. Один из еще одних классических примеров — задача с числами Фибоначчи.</p><p>Числа Фибоначчи — это последовательность, где каждое число равно сумме двух предыдущих: 0, 1, 1, 2, 3, 5, 8, 13, 21, …</p><p>Если вызвать fibonacci(5), то увидите следующее:</p><p>После этого функция возвращает результаты обратно вверх по стеку вызовов. Обычно этот метод используется в криптографии и алгосах.</p><p>Кстати, на примере чисел Фибоначчи можно разобрать одну из проблем рекурсии — плохая производительность при отсутствии мемоизации.</p><p>Мемоизация — техника оптимизации, при которой результаты больших вычислений сохраняются и используются еще раз при повторных вызовах функции. Это особенно полезно, если работаете с рекурсиями, которые могут вычислять одни и те же значения много раз.</p><p>Так, функция  в задаче с числами Фибоначчи имеет экспоненциальную сложность O(2^n), так как она многократно вычисляет одни и те же значения. Например, чтобы вычислить fibonacci(5), функция вызывает сама себя 15 раз, хотя уникальных вычислений всего 6.</p><p>Чтобы улучшить производительность, нужно использовать мемоизацию, чтобы сохранить результаты:</p><p>Здесь сложность уменьшается до O(n), так как каждое значение вычисляется только один раз.</p><h2>Стек вызовов и управление памятью</h2><p>Каждый рекурсивный вызов функции добавляет новый элемент в стек вызовов. Это значит, что вызовы складывается в стеке до тех пор, пока не будет достигнут базовый случай. После этого стек начинает разворачиваться, возвращая результаты на каждом уровне.</p><p>Например:</p><p>Так, когда мы вызываем сountdows(3), в стеке появляются новые вызовы:</p><p>call countdown(3)  # → печатает 3</p><p>call countdown(2)  # → печатает 2</p><p>call countdown(1)  # → печатает 1</p><p>call countdown(0)  # → печатает "Старт" и выходит</p><p>Затем стек начинает разворачиваться:</p><ul><li>countdown(0) завершился → удаляется из стека</li><li>countdown(1) завершился → удаляется из стека</li><li>countdown(2) завершился → удаляется из стека</li><li>countdown(3) завершился → удаляется из стека</li></ul><p>Если же в рекурсии нет базового случая, то стек вызовов никогда не освободится, и программа (в нашем случае на Питоне) выдаст ошибку RecursionError.</p><p>Вот пример бесконечной рекурсии:</p><p>В Питоне есть ограничения до 1000 вызовов, чтобы не перегружать стек, но этот лимит можно увеличить:</p><h2>Рекурсия на реальных задачах</h2><p>С базовым случаем и рекурсивным вызовом все понятно, поэтому давайте разбираться на более реальных и сложных примерах — с деревьями, графами и массивами.</p><h3>Обход структуры данных</h3><p>Рекурсия часто используется для обхода деревьев и графов. Например, бинарного дерева — в нем не более двух дочерних узлов (левый и правый). Вот три основных способа обхода:</p><ol><li>Прямой обход (Pre-order): Корень → Левый узел → Правый узел.</li><li>Симметричный обход (In-order): Левый узел → Корень → Правый узел.</li><li>Обратный обход (Post-order): Левый узел → Правый узел → Корень.</li></ol><p>Запишем в виде кода:</p><p>Дерево будет выглядеть так:</p><p>1</p><p>/ \</p><p>2   3</p><p>/ \</p><p>4   5</p><ul><li>Прямой обход: 1 → 2 → 4 → 5 → 3.</li><li>Симметричный обход: 4 → 2 → 5 → 1 → 3.</li><li>Обратный обход: 4 → 5 → 2 → 3 → 1.</li></ul><p>Обходить можно и графы, особенно в глубине (DFS). Вот пример:</p><p>Сам граф:</p><p>Результат обхода: A → B → D → E → F → C.</p><h3>Разбиение</h3><p>Разбить можно массив или строку. Давайте разбираться на примере строки. Так, рекурсия может разбить строку на слова или разделить ее по конкретным символам. Выглядит это так:</p><h2>Что такое хвостовая рекурсия</h2><p>Хвостовая (оконечная) рекурсия — частный случай рекурсии. Главное отличие от обычной в том, что единственный рекурсивный вызов находится в конце перед выходом функции. В традиционной же рекурсии после рекурсивного вызова могут быть дополнительные вычисления.</p><p>Вот пример:</p><p>Кстати, с помощью хвостовой рекурсии тоже можно решать задачи с числами Фибоначчи:</p><p>Некоторые языки программирования (например, Haskell и Scala) поддерживают оптимизацию хвостовой рекурсии. Это означает, что компилятор или интерпретатор преобразует хвостовую рекурсию в итерацию — это помогает избежать переполнение стека.</p><p>Давайте посмотрим, как это работает в Scala. Вот обычная рекурсия, чтобы понять принцип:</p><p>А вот рекурсия с оптимизацией:</p><p>К сожалению, наш любимый Питон такую функцию не поддерживает, но всегда можно преобразовать «хвостика» в итерацию вручную:</p><p>Это пример итеративного решения факториала.</p><h2>Рекурсия vs. итерация</h2><p>Основные различия между рекурсией и итерацией заключаются в следующем:</p><ul><li>В рекурсии функция вызывает саму себя, а в итерации используются циклы.</li><li>Интеграция требует меньше памяти, потому что нет стека вызовов.</li><li>Рекурсия медленнее без оптимизации.</li><li>С рекурсией легко переполнить хранилище при большой глубине.</li><li>Рекурсия обычно более читаемая и лаконичная, но подходит для меньшего количества задач.</li></ul><p>Итерацию лучше использовать в следующих случаях:</p><ul><li>Задачи, где нужна производительность. Как, например, с итеративным вычислением чисел Фибоначчи.</li><li>Обработка больших данных, например, поиск или сортировка. Вот пример пузырьковой сортировки:</li></ul><ul><li>Задачи с большой глубиной рекурсии, чтобы стек не переполнялся. Вот как итеративно обойти дерево:</li></ul><h2>Еще один пример: задача «Ханойские башни»</h2><p>«Ханойские башни» — классная головоломка, которую часто решают с помощью рекурсии. Считайте, что это еще одна классика.</p><p>Есть три стержня: A, B и C. На стержень A нанизано несколько дисков разного размера, причем диски упорядочены по размеру в виде пирамидки — вниз от маленького к большому. Суть в том, чтобы переместить все диски со стержня А на С, при этом: за один раз можно перемещать только один диск, нельзя класть больший диск на меньший, можно использовать стержень B как вспомогательный.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2025-02-27/c2ccaa24-e4b7-4d2f-8ae8-ac894801834a.png" alt="" /></figure><p>Рекурсивное решение задачи «Ханойские башни» основано на следующей идее:</p><ol><li>Переместить (n−1) дисков с A на B, используя C как промежуточный.</li><li>Переместить оставшийся самый большой диск с A на C.</li><li>Переместить (n−1) дисков с B) на C, теперь используя A как промежуточный.</li></ol><p>Следовательно, если количество дисков n=1, просто переместите его с А на С (базовый случай). В остальных случаях при n–1 повторить то, что указано выше.</p><p>А теперь решение с помощью кода:</p><p>Вывод:</p><p>Поздравляем, вы великолепны. А если хотите еще потренироваться в рекурсии, то можно написать код для поиска пути в лабиринте (кстати, это чем-то напоминает онлайн-карты и навигаторы) или обратного вывода строки.</p>]]></content:encoded>
    </item>
    <item>
      <title>Почему Haskell — лучший выбор для функционального программирования</title>
      <link>https://tproger.ru/articles/vvedenie-v-funkcionalnoe-programmirovanie-na-yazyke-haskell</link>
      <comments>https://tproger.ru/articles/vvedenie-v-funkcionalnoe-programmirovanie-na-yazyke-haskell?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Денис Кудерин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/vvedenie-v-funkcionalnoe-programmirovanie-na-yazyke-haskell</guid>
      <description><![CDATA[<p>Что такое функциональное программирование. Показываем основные особенности на языке Haskell. Рассматриваем пошаговую инструкцию ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/vvedenie-v-funkcionalnoe-programmirovanie-na-yazyke-haskell">Почему Haskell — лучший выбор для функционального программирования</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 27 Oct 2024 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Концепция функционального программирования (ФП) базируется на математических функциях. Такой подход принципиально отличается от императивного, в котором ключевыми элементами выступают изменения состояния кода и последовательное выполнение команд. В ФП основное внимание уделено вычислению тех или иных значений через функции.</p><p>В функциональных языках код проще тестировать, корректировать и поддерживать в рабочем состоянии. Функции в ФП — это объекты первого класса, которые передаются как аргументы, могут быть возвращены из других функций и храниться в переменных.</p><p>Еще одна характерная особенность функционального программирования — более предсказуемый, чистый и безопасный код. Поскольку функции сами по себе не меняют состояния программы, с ними легче работать. По этой причине ФП — более предпочтительный инструмент для создания сложных продуктов, в которых первостепенное значение имеют надежность и предсказуемость кода.</p><p>Haskell входит в число наиболее востребованных функциональных языков программирования. Для него характерна полная, строгая и статическая типизация и поддержка так называемых ленивых вычислений. Первоначально язык применялся в качестве инструмента для сугубо научных математических изысканий, но постепенно стал одним из наиболее востребованных на практике языков.</p><p>Этот материал представляет собой введение в функциональное программирование на языке Haskell. Расскажем об особенностях и преимуществах и как он применяется на практике.</p><h2>Основные концепции ФП</h2><p>Haskell считается сложным языком программирования — в нем используются концепции, которых нет в других языках. Однако эксперты считают, что для разработчиков освоение Haskell станет чрезвычайно полезным опытом.</p><p>Для начала краткая историческая справка. Предтечей Хаскела был другой функциональный язык — Миранда с концепцией «ленивых» вычислений. Финансовая поддержка этого языка была вполне солидной, но особой популярности он не снискал, так как распространялся по частной лицензии.</p><p>В качестве доступной альтернативы Миранде были созданы другие языки, среди которых наиболее популярным стал Haskell. Название язык получил в честь математика из США Хаскелла Карри, создателя комбинаторной логики.</p><p>В 1990 году опубликована первая версия, через 8 лет создатели представили стандарт The Haskell 98 Report. Эта версия до сих пор остается базовой, что не отменяет постоянного развития и совершенствования языка.</p><p>Основное преимущество функционального программирования на Haskell в открытом характере этого инструмента. Разработчики принимают от всех желающих предложения по улучшению и расширению функционала.</p><p>Программа, написанная на Хаскелле, состоит из набора математических функций — определенных соответствий между элементами. Углубляться в алгебраические термины нет смысла, стоит знать лишь о том, для вычисления функций важны исключительно исходные данные.</p><p>В программировании на Haskell собственно функция некоторого множества — основной структурный элемент кода. Задача разработчика — описать функцию так, чтобы автоматическому компилятору, который переводит текст в машинный код, были понятны все значимые моменты:</p><ul><li>какие параметры могут стать функцией;</li><li>какие действия необходимо выполнить;</li><li>как будет выглядеть итоговый код.</li></ul><p>Несмотря на концептуальную сложность, Хаскелл входит в топ наиболее распространенных языков с поддержкой отложенных (они же ленивые) вычислений. Он имеет встроенную поддержку параллельного программирования и обширный инструментарий — это и средства автоматической проверки и отладки, и тысячи библиотек, созданных пользователями всего мира.</p><h2>Характеристики Haskell</h2><p>Прежде чем перейти к более узким темам, стоит рассмотреть наиболее важные характеристики Хаскелл:</p><h3>Ленивость (отложенность)</h3><p>Это означает, что вычисление функций происходит по мере надобности. Если для выполнения программы значение конкретной функции не требуется, вычисление откладывается на потом.</p><p>Пример практического применения принципа ленивости — обычный калькулятор на сайте, в котором заложено множество функций, но в конкретный момент используются только нужные пользователю.</p><p>Преимущество такого принципа в том, что разработчик имеет возможность взаимодействовать с почти бесконечными структурами данных. Можно сравнить это с чтением научных статей, в которых есть формулы. Когда читатель доходит до таких мест, от может пропустить их и вернуться позже, как только будет готов сконцентрироваться.</p><p>При изучении Хаскелла действительно не так важно, если программист не совсем понимает некоторые моменты синтаксиса. Главное — это уловить общую идею кода.</p><h3>Статическая типизация</h3><p>Под типом данных понимается определенная категория элементов, к которой можно применить какие-либо операции. Тип определяет характеристики данных, их возможные значения и операции, которые могут быть с ними проделаны.</p><p>Система типов в Haskell строгая и статичная. Это означает, что они имеют четкие различия, которые задаются еще на стадии компиляции, а не при исполнении программы.</p><p>При этом в Хаскелл присутствует особый тип данных под названием монады. Это своего рода контейнер, который содержит значения произвольного типа. Посредством монад на языке Haskell можно выполнять ряд действий, характерных для императивного программирования. К примеру, задавать последовательные операции, использовать функции с побочными значениями.</p><h3>Функциональность</h3><p>Разработчикам, которые работали только с императивными языками, придется освоить множество новых концепций. Однако определенные сложности пойдут только на пользу программисту — он приобретет новые практические навыки. Эксперты утверждают, что после работы с Хаскелл становится проще кодировать на С++, Java и иных языках.</p><h3>Чистота</h3><p>Функциональный язык Haskell работает исключительно с чистыми функциями. Для них характерны такие свойства, как:</p><ul><li>строгая детерминированность — одному значению аргумента соответствует одно значение программной функции;</li><li>отсутствие побочных действий — то есть влияния функции на среду исполнения.</li></ul><p>Есть и исключения: для некоторых задач требуется как недетерминированность, так и побочные эффекты. Эти возможность реализованы посредством монад.</p><p>В целом здесь действует основной закон Хаскелла: <i>использование функции с одинаковыми параметрами приводит к одному и тому же результату. </i></p><h3>Модульность</h3><p>Все программы на функциональном языке Haskell — это совокупность автономных блоков, каждый из которых выполняет свою задачу. Код разбит на независимые файлы, в которых содержатся модули.</p><p>Подобная структура существенно упрощает создание и тестирование программных продуктов. Если потребуется улучшить код или исправить ошибку, сделать это можно в отдельном модуле, не затрагивая всю программу. Модульность ускоряет разработку ПО, разделяя отдельные блоки между разными командами разработчиков.</p><h3>Параметрический полиморфизм</h3><p>В Хаскелле можно оперировать значениями разных типов одинаковым образом. Это свойство делает язык более выразительным и позволяет неоднократно пользоваться конкретным фрагментом кода для работы с различными данными, что значительно упрощает разработку ПО.</p><p>На практике это означает, что программеру не нужно писать разный код для каждого вида данных — можно использовать подходящий из тех, что уже имеются в наличии.</p><h2>Преимущества и недостатки</h2><p>Ключевые преимущества функционального языка Haskell:</p><ul><li>Слои языка четко подразделяются на «чистые» и «нечистые», поэтому разработчикам проще работать с каждым блоком по отдельности. Благодаря такому разделению код лучше понятен не только тем, кто с ним работал, но и сторонним программистам.</li><li>Разделение кода на независимые блоки существенно упрощает тестирование продуктов. Находить и исправлять ошибки в независимых модулях проще, при этом не приходится переписывать весь код. Устойчивость программы к ошибкам повышается, поскольку они локализованы в отдельных модулях.</li><li>Благодаря свободному доступу созданы тысячи инструкций, библиотек и рекомендаций для начинающих и опытных программеров на Хаскелл. Активное комьюнити профи и любителей поможет решить проблемы в процессе программирования — всегда можно запросить помощь или найти готовое решение.</li><li>Поскольку типы назначаются и проверяются еще на стадии компиляции, этим не придется заниматься в процессе работы программы. Благодаря ленивости ПО не тратит ресурсы на вычисление функций, не задействованных в настоящий момент. Скорость реализации кода повышается, проще включать параллельное вычисление и работать в режиме многозадачности.</li><li>Для Haskell создано множество разных инструментов проверки, отладки и интеграции с продуктами на других языках. По этой причине Хаскелл успешно применяется в практических целях, в том числе для работы с математическими задачами и для разработки утилитарных продуктов.</li></ul><p>Эксперты отмечают и недостатки:</p><ul><li><b>Сложный синтаксис. </b>Поначалу он может показаться нерациональным, особенно опытным разработчикам, привыкшим к языкам программирования со стандартным синтаксисом. Как ни странно, начинающим программистам освоить Haskell будет проще, поскольку у них нет конкретных предпочтений в парадигмах языков.</li><li><b>Неравномерное развитие на разных платформах.</b> Разработчики изначально ориентировались на Linux и MacOS, поэтому те, кто работает на Windows, сталкиваются с определенным дефицитом инструментов и прочими проблемами.</li><li><b>Сложность в решении нестандартных задач. </b>В репозиториях и библиотеках собрано множество решений, но значительная их часть посвящена работе с типовыми проблемами. Нестандартные задачи приходится решать самостоятельно либо просить помощи у сообщества.</li></ul><p>Серьезной проблемой можно назвать отсутствие эффективных сред разработки. Те, что есть в наличии, оснащены ограниченным набором опций типа автозаполнения либо подсветки синтаксиса. Такие возможности, как навигация или рефакторинг (переформатирование структуры без изменения поведения программы) практически отсутствуют.</p><h2>Основы языка Haskell</h2><p>Итак, чистая функция всегда показывает стабильный результат при одних и тех же вводных данных. Она не меняет состояния кода и не зависит от внешнего влияния. Такой код проще тестировать по причине его предсказуемости.</p><p>В примере функция add демонстрирует, что для одинаковых значений «x» и «у» итог всегда будет одинаковым. Эта особенность чистых функций чрезвычайно важна и полезна в ФП.</p><p>Неизменяемость данных (она же иммутабельность) означает, что созданные значения невозможно изменить, можно только ввести новые значения. Такое свойство исключает ошибки, связанные с изменением статуса, и делает код более предсказуемым.</p><p>В примере «x» не меняется, даже если создано новое значение «y». Такое свойство упрощает работу с кодом — можно не беспокоятся, что значения изменятся непредсказуемым образом.</p><p>Благодаря функциям высшего порядка, которые применяют другие функции как аргументы, разработчики издают более абстрактный, но при этом более эффективный код. Такие программы проще адаптировать и расширять.</p><p>В примере функция applyTwice принимает значение «x». Таким образом функция используется для разработки более мощных структур.</p><p>Процесс каррирования — это преобразование функции, принимающей ряд аргументов, в функцию, которая принимает один аргумент. Процедура повышает гибкость программных элементов.</p><p>Здесь функция «add» каррирована, что приводит к созданию другой функции «addFive», которая всегда прибавляет 5 к аргументу. Таким образом, функцию можно использовать в разных контекстах.</p><h2>Лямбда-выражения и высшие функции</h2><p>В написании кода на Haskell функции определяет команда «let», после чего следует указание имени, типа аргументов и тела функции.</p><p>В примере простая функция add, суммирующая два значения. На аналогичной основе создаются более сложные программы.</p><p>Итерации выполняются в Хаскелл с помощью рекурсивных функций, а не циклов, как в более привычных нам императивных языках.</p><p>В примере факториал вычисляется с помощью рекурсии.</p><p>К важным структурам языка относятся списки. Их применяют для отображения последовательностей с помощью различных функций.</p><p>Здесь список содержит числа, а функция используется для удвоения элементов. Операции со списками разработчикам стоит освоить в первую очередь.</p><p>Для кратковременных операций применяют анонимные лямбда-функции. С их помощью можно создавать функции без имени.</p><p>Простой пример применения лямбда-функции для суммирования двух чисел:</p><p>Инструмент полезен для создания локальных функций с ограниченным использованием.</p><h2>Практическое применение Haskell</h2><p>В специальной литературе можно встретить утверждение, что Хаскелл — язык гениев и академиков. Это верно лишь отчасти. Первый функциональный язык действительно не прост для освоения, но лишь до того момента, пока вы пытаетесь понять его концепцию в рамках парадигмы императивных языков. Если подойти к нему непредвзято, понять Хаскелл будет гораздо проще.</p><p>Применение на практике Haskell — это разработка видео-серверов, обработка сложных массивов данных, другие многосоставные задачи.</p><p>Крупные банки и инвестиционные компании используют функциональный язык для разработки персональных рабочих инструментов. Хаскелл гарантирует точность и корректность вычислений независимо от уровня их сложности.</p><p>Кроме того, на этом языке работают программы синтаксического разбора, редактирования текста, некоторые поисковые сервисы и программы по удалению спама и рекламы. Структура Haskell наиболее удобна для отображения грамматических и прочих правил самых сложных языков, включая русский.</p><p>Чтобы начать работу с Haskell, потребуется установить компилятор Glasgow Haskell Compiler с пакетным менеджером Stack — он упрощает сборку проектов.</p><p>После установки откройте терминал введите команду «ghci» для запуска интерактивной оболочки языка. Несколько простых выражений помогут убедиться, что система работает:</p><p>Интерактивная оболочка позволяет свободно работать с кодом и тестировать его. Для изучения языка ее применение обязательно.</p><p>Чтобы создавать проекты, понадобится менеджер Stack. Для запуска нового проекта введите «stack new my-project» — эта команда предоставит структуру и установит нужные зависимости.</p><p>Основной файл — это Main.hs. Его нужно открыть и добавить:</p><p>Эта простая программа узнает имя пользователя и приветствует его.</p><p>Для компиляции и запуска применяются команды:</p><p>Менеджер Stack — наиболее удобный инструмент для кодирования на Хаскелл.</p><h2>Итоги</h2><p>Язык функционального программирования Haskell содержит мощные и эффективные инструменты для написания чистого кода. Начинающие разработчики, освоив базовые концепции, смогут постепенно перейти к более сложным аспектам функциональной парадигмы. Следующий шаг — создание надежных и предсказуемых систем, применяемых в различных отраслях программирования.</p>]]></content:encoded>
    </item>
    <item>
      <title>Редкие языки программирования: зачем они нужны и что на них пишут</title>
      <link>https://tproger.ru/articles/redkie-yazyki-programmirovaniya--zachem-oni-nuzhny-i-chto-na-nih-piwut</link>
      <comments>https://tproger.ru/articles/redkie-yazyki-programmirovaniya--zachem-oni-nuzhny-i-chto-na-nih-piwut?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/redkie-yazyki-programmirovaniya--zachem-oni-nuzhny-i-chto-na-nih-piwut</guid>
      <description><![CDATA[<p>Что такое редкие языки программирования и зачем они нужны: COBOL для финансов, Lua для игр, Haskell, F#, Elixir, R и Clojure. Читайте на Tproger!</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/redkie-yazyki-programmirovaniya--zachem-oni-nuzhny-i-chto-na-nih-piwut">Редкие языки программирования: зачем они нужны и что на них пишут</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Язык R]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 12 Jul 2024 09:30:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сейчас самые <a href="https://www.tadviser.ru/index.php/%D0%A1%D1%82%D0%B0%D1%82%D1%8C%D1%8F:%D0%A0%D0%B5%D0%B9%D1%82%D0%B8%D0%BD%D0%B3_%D0%B2%D0%BE%D1%81%D1%82%D1%80%D0%B5%D0%B1%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F#:~:text=%D0%A1%D1%80%D0%B5%D0%B4%D0%B8%20%D0%BF%D1%80%D0%BE%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BA%D0%BE%D0%BC%D0%BF%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%B2%20%D1%81%D0%B0%D0%BC%D1%8B%D0%B5,%D0%BC%D0%BB%D0%BD%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D1%83%D0%B6%D0%B1%D1%8B%20%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D0%BD%D0%BE%D0%B9%20%D1%87%D0%B0%D1%81%D1%82%D0%B8">популярные</a> языки программирования — JavaScript (с ним работают 25,2 млн разработчиков), Python (18,2 млн) и Java (17,7 млн). Но иногда проект требует специфичного кода, который невозможно написать с помощью топа популярных технологий. Ниже — подборка редких языков программирования, о которых многие девелоперы даже не слышали, но которые могут дать большой буст разработке.</p><h2>COBOL</h2><p>COBOL (Common Business Oriented Language) появился в 1959 году благодаря Грейс Хоппер (ее еще называют «бабушкой Кобала»). Изначально он был императивным и процедурным, а с 2002 года стал объектно-ориентированным. Распространился он по инициативе Минобороны США, а затем стал широко использоваться в финансовых вычислениях.</p><figure><img src="https://media.tproger.ru/user-uploads/101078/2024-07-10/6172cd13-300f-4a1f-9f9f-999766a1b5c9.jpg" alt="" /></figure><blockquote><i>«Использование Кобола калечит ум. Его преподавание, следовательно, должно рассматриваться как уголовное преступление» — Эдсгер Дейкстра</i></blockquote><p>В какой-то степени Дейкстра прав — у Кобола действительно сложный и громоздкий синтаксис в отличие от менее «многословных» языков. Тем не менее он крут в задачах, с которыми не справляются другие технологии. Дело в том, что Кобол проводит десятичные вычисления не с плавающей, а с фиксированной точкой — не нужны сложные правила округления, как, например, на Java, и операции производятся быстрее. К тому же код будет одинаково работать на 8-битной и 64-битной архитектурах, поскольку COBOL может хранить переменные разных типов вне зависимости от компилятора и архитектуры системы.</p><p>Именно поэтому Кобол незаменим в денежных операциях. В США язык используют банки и система здравоохранения — транзакции через терминалы, торговля на бирже и зарплаты врачам проходят именно через него.</p><p>Вот пример «простенького» кода:</p><p>Не спорим, выглядит не так элегантно, как Python, но зато все деньги и суммы будут подсчитаны верно, поскольку округлением чисел занимается не процессор и арифметико-логическое устройство, а человек.</p><h2>Lua</h2><p>Lua — это скриптовый язык программирования с исходным кодом на C. Ближе всего он к JavaScript с такой же прототипной моделью ООП, но LUA более мощный и гибкий. Массивы, структуры, множества и остальные составные пользовательские типы данных реализуются с помощью таблиц, а механизмы ООП — через метатаблицы.</p><p>Изначально язык разрабатывался для промышленности, скорее, для конкретной нефтяной компании Petrobras. Сейчас же его используют для разработки массового ПО, например, для графического интерфейса Adobe Lightroom, и игр. Так, с помощью Луа можно легко закодить поведение NPC и других персонажей, под которых не придётся переписывать движок. Среди таких игр — Garry's Mod, Roblox, Stalker и даже World of Warcraft.</p><p>Вот пример кода из «Сталкера»:</p><p>Кстати, потренировать свои навыки Lua можно в игре Retro Gadgets. Рассказывали о ней в нашей <a href="https://tproger.ru/digest/igraya-uchites--podborka-topovyh-igr-po-programmirovaniyu">статье</a>.</p><h2>F#</h2><p>F Sharp — мультипарадигмальный язык, который поддерживает не только процедурное и объектно-ориентированное программирование, но еще и функциональное. Его разработали в Microsoft Research, а затем компания интегрировала среду разработки F# в Visual Studio 2010 и последующие версии.</p><p>У F Sharp лаконичный код с выводом типов. Он ориентирован на данные, параллельный ввод-вывод, параллельные вычисление и часто используется в научных исследованиях, анализе данных, машинном обучении и веб-разработке. К тому же он интегрируется с .NET платформами.</p><p>Язык действительно редкий и непопулярный, а специалистов, знающих его, в <a href="https://gb.ru/posts/f_sharp">100 раз меньше</a>, чем С#-разработчиков. Однако в некоторых специфических задачах, связанных с Big Data и ML, «фаршик» может быть крайне полезен.</p><p>Вот <a href="https://gist.github.com/odytrice/667bc1d8d7c872fe8c5b1baa58898c32">пример</a> кода:</p><h2>R</h2><p>Еще один язык для подсчета цифр и чисел — R был создан специально для статистического анализа данных. В общем, очередное машинное обучение и Big Data. Он появился в Оклендском университете взамен платному и недоступному большинству разработчиков языку S.</p><p>Сейчас популярность R начинает расти, и им пользуются многие крупные компании, например, Amazon, Deloitte, Accenture, Google и другие. Тем не менее синтаксис не похож на другие языки — это в первую очередь статистическая технология, поэтому начинающим разработчикам разобраться в нем будет непросто. Среди функций — объединение данных из таблиц, создание интерактивных графиков, статистическое тестирование. Кстати, на R можно написать и веб-приложение с помощью библиотеки Shiny.</p><p>Вот пример кода, который делает из двоичного кода десятичный:</p><h2>Clojure</h2><p>Clojure — это современный, динамичный и функциональный «диалект» языка Lisp на платформе Java. Появился в 2007 году благодаря Ричу Хики. Разработан как язык общего назначения — он сочетает доступность и интерактивную разработку языка сценариев с надежной инфраструктурой для многопоточного программирования. Clojure известен своей интерактивной средой REPL и мощной поддержкой параллельного программирования.</p><p>Одна из ключевых характеристик языка — функциональность, а это значит, что в нем особое внимание уделяется применению функций и неизменности данных. Он также предоставляет доступ к Java-фреймворкам и может компилироваться как с байт-кодом Java, так и с JavaScript — это полезно и для серверной, и для клиентской разработки.</p><p>Clojure используют в крупных банках, например, в Raiffeisen, а также в Netflix, Amazon, Apple и других гигантах. Так, в Apple с помощью него <a href="https://grishaev.me/clojure-article/">собирают</a> данные и статистику.</p><p>Вот, например, код <a href="https://github.com/gusenov/examples-clj/blob/master/hello/hello.clj">Hello, world</a> на Conjure:</p><h2>Haskell</h2><p>Еще один чистый функциональный язык программирования. Среди особенностей — строгая типизация, почему он и назван в честь Хаскелла Карри, исследователя теории типов и создателя комбинаторной логики. Также язык поддерживает отложенные вычисления.</p><p>Haskell отличается не только простым и элегантным синтаксисом, но и быстротой и производительностью. Он применяется в инвестиционном банкинге, а также для создания системных продуктов и ПО. Разумеется, его тоже используют в крупных компаниях: в Meta*, IBM, Twitter, Bank of America и других. Например, Facebook* борется с помощью Haskell со спамом.</p><p>Очередной Hello World, только на Haskell:</p><p>Да, это все. putStrLn можно заменить на print.</p><h2>Elixir</h2><p>Тоже язык программирования общего назначения, который использует динамическую типизацию данных и функциональную парадигму. Elixir использует виртуальную машину Erlang, известную своей способностью запускать распределенные и отказоустойчивые системы с низкой задержкой.</p><p>Среди особенностей — возможность метапрограммирования, которые позволяют разработчикам писать код, который сам пишет код, и поддержка параллельного программирования. Elixir также предоставляет мощные встроенные инструменты для тестирования и отладки программного обеспечения.</p><p>Язык широко используется в веб-разработке, особенно в среде Phoenix Framework, а также при создании распределенных и отказоустойчивых систем, благодаря базовой платформе Erlang/OTP. Для разработчиков, не знакомых с парадигмами функционального программирования, освоить будет не так просто, зато какие преимущества — эффективность, масштабируемость и надежность приложений.</p><p>Вот код, который продублирует строки множество раз:</p><p>На самом деле, нишевых и редких языков программирования очень много. И даже опытные разработчики могли никогда о них не слышать. Зато такие языки помогают решать специфические задачи, когда популярные решения бессильны.</p><p><i>Делитесь в комментариях, на каких редких языках ходили и как вам это помогло в разработке проекта.</i></p><p>*Компания Meta признана экстремистской и запрещена на территории РФ</p>]]></content:encoded>
    </item>
    <item>
      <title>«Какой функциональный язык программирования стоит выбрать начинающему разработчику?» — советы от пользователей Reddit</title>
      <link>https://tproger.ru/articles/-kakoj-funkcionalnyj-yazyk-programmirovaniya-stoit-vybrat-nachinayushhemu-razrabotchiku-----sovety-ot-polzovatelej-reddit</link>
      <comments>https://tproger.ru/articles/-kakoj-funkcionalnyj-yazyk-programmirovaniya-stoit-vybrat-nachinayushhemu-razrabotchiku-----sovety-ot-polzovatelej-reddit?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Мария Кривоченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/-kakoj-funkcionalnyj-yazyk-programmirovaniya-stoit-vybrat-nachinayushhemu-razrabotchiku-----sovety-ot-polzovatelej-reddit</guid>
      <description><![CDATA[<p>Начинающий разработчик решил изучать функциональное программирование и не уверен, какой язык ему выбрать. Рассказываем, что ему посоветовали.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/-kakoj-funkcionalnyj-yazyk-programmirovaniya-stoit-vybrat-nachinayushhemu-razrabotchiku-----sovety-ot-polzovatelej-reddit">«Какой функциональный язык программирования стоит выбрать начинающему разработчику?» — советы от пользователей Reddit</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Scala]]></category>
      <category><![CDATA[Lisp]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 23 Jan 2024 16:40:16 GMT</pubDate>
      <content:encoded><![CDATA[<p>Недавно пользователь под ником HistoricalAccess9501 рассказал, что интересуется функциональным программированием, но не знает, с какого языка ему начать:</p><p>Множество людей советовали мне изучить Haskell, Scala, Clojure, Erlang или Lisp, потому что они помогут хорошо понять функциональное программирование. Но я не уверен, за какой стоит взяться в первую очередь. У меня есть опыт работы с Python и JS, но я ни разу не сталкивался с функциональными языками программирования, поэтому также хочется знать, какие языки для чего используются: приложения, сайты, игры, машинное обучение и так далее.</p><p>В ответ HistoricalAccess9501 получил несколько дельных советов.</p><p><b>UdPropheticCatgirl</b></p><p>Я думаю, что любой из потомков и диалектов Lisp подойдет, например, SBCL или Clojure (и, возможно, Racket).</p><p>Можно взяться за Elixir, Erlang и Gleam, особенно чтобы разобраться в BEAM. Если вы хорошо знакомы с акторами и IPC. Scala великолепен, но многим отличается от большинства функциональных языков, поэтому не стоит учить его в первую очередь. Еще я бы не брался за Haskell и вместо него учил OCaml.</p><p>Что касается сфер применения:</p><ul><li>Scala используется для построения дата-пайплайнов, и для бэкенда на Java.</li><li>Clojure — оптимальный Lisp, для работы в 2024, в основном если пишете бэкенд на Java.</li><li>SBCL сегодня встречается редко, я сталкивался с ним только во время работы с HFT, еще в какой-то момент его часто использовали в сфере ИИ, но не уверен, продолжают ли сейчас.</li><li>Elixir — снова бэкенд, особенно если работаете с распределенными системами.</li><li>Erlang — король распределенных систем, часто используется в телекоме.</li><li>Haskell подходит, только если вам не о чем поговорить с курьером и перед партией в D&amp;D.</li><li>OCaml используется примерно в двух финтех-компаниях и некоторыми небольшими анализаторами в Meta, вот и все.</li></ul><p><b>VicariousAthlete</b></p><p>F# просто замечательный — мой лучший опыт в этой сфере. Сейчас мы используем его в создании масштабных систем (заказ еды онлайн).</p><p><b>nandryshak</b></p><p>Сначала определите, какие концепции вы хотите изучить, а потом подберите под нее язык. Не забудьте про популярность языка — хорошее сообщество и большой набор инструментов упростят обучение.</p><ul><li>Функции первого класса, составные функции и прочее? — Javascript, Racket.</li><li>Все вышеперечисленное плюс неизменяемые объекты? — Clojure.</li><li>Все вышеперечисленное плюс хорошая система типов? Typescript.</li><li>Все вышеперечисленное плюс, Хиндли-Милнер и математика? — Haskell.</li></ul><p>&lt;…&gt;</p><p><b>Joewoof</b></p><p>Сложно выбрать. Самый простой и популярный — Scala, но это мультипарадигмальный язык. Haskell слишком редкий. Что-то среднее?</p><p>Наверное, OCaml. Он преподается как первый в Кембридже, у него хорошая репутация. Если хотите что-то более практичное, то выбирайте Elixir.</p><p><b>0xAERG</b></p><p>Если вы знакомы с JS и хотите что-то похожее, советую попробовать изучить ReScript. Он функциональный, компилируется в JavaScript, а синтаксис похож на смесь OCalm и JS. &lt;…&gt;</p><p><b>davadds0</b></p><p>В университете я изучал Erlang и Scheme.</p><p>Рекомендую сначала изучить Erlang (сначала хорошо освоить vanilla Erlang), а затем — OTP, что-то вроде библиотеки/фреймворка Erlang, это спасет вас от монотонной работы. А еще будет удобно создавать сложные распределенные системы с помощью Erlang/OTP. &lt;…&gt;</p><p><b>Peiple</b></p><p>Из необычного — R, который полезен на практике. Он не такой хардкорный, как Haskell, но содержит большинство концепций функционального программирования. В основном используется в Data Science. &lt;…&gt;</p><p><b>Frenchslumbe</b></p><p>Самый легкий в освоении — Lips.</p><p>Простой и последовательный синтаксис (но при этом очень выразительный). К тому же, изучив Lips, будет проще с его диалектами и языками-потомками. &lt;…&gt;</p><p>Также советую ClojureScript. Это реализация Clojure, с компиляцией в JavaScript.</p><p>А что бы вы посоветовали, начинающим изучать функциональное программирование?</p>]]></content:encoded>
    </item>
    <item>
      <title>Вышла бета-версия TabNine, «умного» инструмента автозаполнения для редакторов кода</title>
      <link>https://tproger.ru/news/tabnine-tool-beta</link>
      <comments>https://tproger.ru/news/tabnine-tool-beta?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Екатерина Никитина]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/tabnine-tool-beta</guid>
      <description><![CDATA[<p>Инструмент на основе модели GPT-2 натренирован на 2 миллионах файлов с GitHub, работает с Python, Java, C++ и Haskell и дружит с VS Code</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/tabnine-tool-beta">Вышла бета-версия TabNine, «умного» инструмента автозаполнения для редакторов кода</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 19 Jul 2019 11:09:14 GMT</pubDate>
      <content:encoded><![CDATA[<p>Началось бета-тестирование TabNine — это «умный» инструмент, который предлагает варианты автозаполнения во время написания кода. Его тренировали на 2 миллионах файлов с GitHub. Инструмент работает с несколькими языками: Python, Java, C++ и Haskell.</p><p>Вообще инструмент основан на модели GPT-2, предназначенной для обработки естественного языка. Но разработчики приспособили его и для понимания кода. К тому же, благодаря этому TabNine разбирается в документациях, а те как раз написаны на естественном английском.</p><p>TabNine совместим с VS Code, Sublime Text, Atom, Vim и прочими редакторами кода. Послать заявку на участие в бета-тестировании можно на сайте проекта.</p>]]></content:encoded>
    </item>
    <item>
      <title>Курс «Функциональное программирование»</title>
      <link>https://tproger.ru/video/func-programming</link>
      <comments>https://tproger.ru/video/func-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дмитрий Баранчук]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/func-programming</guid>
      <description><![CDATA[<p>Русскоязычный видеокурс от Computer Science Center «Функциональное программирование»</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/func-programming">Курс «Функциональное программирование»</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 21 Sep 2017 10:15:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>Русскоязычный курс от Computer Science Center, знакомящий слушателей с функциональными языками программирования и методами программирования на этих языках. Курс записан в 2015 году, лектор — Денис Москвин.</p><p>Курс рассматривает отличия функционального подхода к программированию от традиционного императивного, знакомит слушателей с лямбда-исчислением как теоретической основой функционального программирования, системами типов функциональных языков, алгоритмом вывода типов Хиндли-Дамаса-Милнераи. Параллельно с этим в курсе подробно изучаются основы языка программирования Haskell.</p><p>Смотрите еще больше материалов по функциональному программированию.</p>]]></content:encoded>
    </item>
    <item>
      <title>Подборка ресурсов с примерами кода на разных языках</title>
      <link>https://tproger.ru/digest/learn-everything-by-examples</link>
      <comments>https://tproger.ru/digest/learn-everything-by-examples?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/digest/learn-everything-by-examples</guid>
      <description><![CDATA[<p>Сайты с простыми и понятными примерами кода на Java, C# и других языках — альтернатива документации и длинным туториалам для тех, кому они не по душе.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/digest/learn-everything-by-examples">Подборка ресурсов с примерами кода на разных языках</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Ruby]]></category>
      <category><![CDATA[PHP]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Подборки]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 04 Sep 2016 00:46:12 GMT</pubDate>
      <content:encoded><![CDATA[<p>Для тех, кому тяжело разбираться в документации и не по душе длинные туториалы, мы сделали подборку сайтов со множеством простых и понятных примеров кода на разные темы и на разных языках. Многие из них дают хорошее представление о подходах, которые реализуют. Кстати, если совсем хотите упростить себе жизнь, то взгляните на нашу <a href="https://tproger.ru/digest/trysmthorg/">подборку</a> интерактивных самоучителей.</p><h4>Java</h4><p>На <a href="http://www.java-examples.com">Java Examples</a> можно найти примеры на любой случай — от использования ключевых слов, вроде final, до объяснения, как выровнять иконку в Swing.</p><h4>C#</h4><p>Сайт для шарпистов <a href="http://www.csharp-examples.net">С# Example</a> вышел несколько более бедным, чем посвященный Java. Здесь вы не найдёте всех примеров, которые вам могут понадобиться, но можете найти что-то специфическое, например, <a href="http://www.csharp-examples.net/combine-multiple-printdocuments/">как совместить несколько PrintDocument’ов</a>.</p><h4>JavaScript</h4><p>Отдельный <a href="http://javascriptbook.com/code/">раздел</a> JavaScript Book построен в форме самоучителя. Всего есть 13 тем, например: API, Error handling, jQuery, Events. Каждый из них содержит около десяти примеров. Правда, есть весомый минус — код выдаётся в виде текстового файла, без подсветки.</p><h4>Haskell</h4><p>Отличный способ изучить основы Haskell — ознакомиться с содержимым сайта <a href="https://lotz84.github.io/haskellbyexample/">Haskell By Examples</a>. Здесь коротко описаны все базовые возможности языка, а примеры снабжены ссылками на <a href="https://wiki.haskell.org/">Haskell Wiki</a>.</p><h4>Ruby</h4><p>Существует и аналогичный ресурс по Ruby.</p><h4>C++</h4><p>Наверное, самый приятный сайт из этой подборки — это <a href="http://www.cppsamples.com">Cpp Examples</a>. Красивый дизайн, подсветка кода, разнообразные сэмплы. Есть, несколько посвящённых <a href="https://tproger.ru/translations/design-patterns-for-beginners/">паттернам</a>, например, <a href="http://www.cppsamples.com/patterns/observer.html">Observer</a> или <a href="http://www.cppsamples.com/patterns/builder.html">Builder</a>.</p><h4>PHP</h4><p>Создатели русскоязычной подборки примеров с PHP.su сделали упор на практическое применение, а не на обучение новичков. Здесь не будет примеров, посвящённых ключевым словами и работе с переменными, но можно узнать, как сделать свою рассылку или гостевую книгу.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как начать писать на Haskell: опыт Python-программиста</title>
      <link>https://tproger.ru/translations/becoming-productive-in-haskell</link>
      <comments>https://tproger.ru/translations/becoming-productive-in-haskell?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/becoming-productive-in-haskell</guid>
      <description><![CDATA[<p>Мэтью Гриффин перешёл с динамического Python на статический Haskell и делится принципами: прежде всего данные, описанные прямо при объявлении.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/becoming-productive-in-haskell">Как начать писать на Haskell: опыт Python-программиста</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 23 Jul 2016 21:50:38 GMT</pubDate>
      <content:encoded><![CDATA[<p><i>Рассказывает автор блога Mechanical Elephant, Мэтью Гриффин</i></p><p>После продолжительного изучения Haskell я набрался достаточно опыта, чтобы сейчас дать вам пару советов. Кроме того, я хотел бы усвоить для себя кое-какие принципы, прежде чем идти дальше.<br />И хотя я иногда прибегаю к помощи Python, большую часть работы в вебе я теперь делаю на Haskell.</p><h3>В первую очередь — данные</h3><p>Я думал о переходе от динамического языка к статическому, а в Haskell’e структура данных, с которыми вы работаете, четко описывается при объявлении. В Python в большинстве случаев эту задачу выполняет код.</p><p>Когда я впервые увидел функции в Haskell, я задался вопросом: «Что представляют собой данные? Эта функция что-то берет на вход и выдает что-то на выходе?» А при работе с Python у меня возникал вопрос: «WHAT DOES THE CODE SAY?»</p><p>Структура данных Haskell сформировала качественно новый образ моего мышления, который я принес и в свою работу на Python. Мой код стал заметно лучше. И хотя очень часто мне казалось, что форма описанных мною данных менялась без причины, однако на деле все становилось ясно при небольшом изучении вопроса. Ограничения свободы в изменении данных также делают код менее сложным и более понятным.</p><h3>Читаемость кода</h3><p>Python привлекал меня возможностью писать действительно читаемый код. Примеры кода же на Haskell выглядели просто ужасно, за исключением некоторых сниппетов, которые, казалось, были подобраны специально, чтобы не напугать новичков. И хотя некоторые куски кода смотрелись очень даже приятно, большая часть исходников была наполнена чем-то страшным. А за этим «страшным» как раз и скрывалась вся мощь языка.</p><p>Я определенно не хотел писать «умный» код — тот, который способен продемонстрировать впечатляющие возможности языка, но совершенно неудобен.</p><p>Однако я оценивал читаемость языка Haskell в сравнении с другими популярными языками программирования. Это было похоже на то, как если бы я оценивал китайский язык, будучи носителем английского языка.</p><p>Я понял, что Haskell — это язык не «умный», но с хитринкой. Написать «умный» код на Haskell, конечно, можно, но это не распространенный случай. В то же время, мощь «умного» кода ограничена строгой типизацией. Если функция должна вернуть Int, она вернет Int. Ну или выкинет ошибку компиляции в крайнем случае.</p><p>А более мощные абстракции в Haskell и вовсе напоминают некую магию, которой я пытался избегать при работе с Python.</p><h3>Про читаемость я говорю серьезно</h3><p>Сначала поверьте и убедите себя, что, да, люди действительно читают листинги на этом новом для вас языке, причем делают это быстро и регулярно. Разобравшись наконец с большей частью известных нюансов, я начал спокойнее относиться к коду на Haskell.</p><p><b>Комментарии. </b>Они занимают верхнюю строчку в одной из глав нашей «книги».</p><p>Эта глава описывает то, как Томми пошел в магазин и купил утку.</p><p><b>Функции </b>из другой, уменьшенной функции, в общей картине сокращают код по максимуму.</p><p><b>Краткость.</b> Вам не потребуется тонна кода для воплощения ваших идей.</p><h3>Вставные символы</h3><p>Я также хотел упомянуть о вставных функциях, которые распространены в языке Haskell. Вставные функции (или операторы) — это те функции, которые используют <i>between</i> для двух аргументов вместо before. Простым примером является «+».</p><p>В языке Haskell мы имеем несколько вставных символов, которые используются по умолчанию: $, &lt;$&gt;, &lt;-, -&gt;, др. Они могут вас немного смутить на начале пути.</p><p>Не переживайте! Как только вы научитесь применять их, вы поймете, как они полезны и просты. Я насчитал около 5 символов, которые я использую регулярно.</p><p>Я не советую сразу пользоваться библиотекой lens, так как она содержит тонну таких символов. Данная библиотека очень полезна, но поначалу вы сможете добиться больших успехов и без нее. Дождитесь того момента, когда вы сможете спокойно писать программы средней сложности на Haskell, затем начните использовать lens.</p><h3>Нужно полностью обновить знания</h3><p>При изучении Haskell вы будете попутно сталкиваться с новыми терминами, например, функтор или монада.</p><p>Вам может показаться, что эти слова сложно запомнить и выучить. Когда вы начинаете знакомиться с популярным языком программирования, многие термины вам понятны и знакомы из изученных вами языков.</p><p>Мы запоминаем информацию путем ассоциирования ее с другой информацией. Например, у меня нет ассоциаций со словом функтор, поэтому выучить этот термин мне будет не легко.</p><p>Моя стратегия по изучению таких слов пришла ко мне в голову совсем недавно, и я начал использовать ее каждый раз, когда видел непонятный мне термин. Она заключается в подборе синонимов к тем словам, которые вам не понятны и незнакомы. Спустя некоторое время, я научился подбирать эти синонимы.</p><p>Например, <i>функтор.</i></p><p>В языке Haskell многое отображается на вашем экране. Например, список — это и есть функтор. Это значит, что отображающая функция, которая использует другую функцию, и выводится в списке. Затем создается новый список с результатами.</p><p>Так я дал название этому — мапа. Слово «мапа» является очень простым для запоминания. Список — это функтор. Список — это мапа.</p><h3>Моя система проверки ошибок</h3><p>Когда я писал на Python, моим инструментом отладки были операторы печати.</p><p>В Haskell я пользовался систематическими инструментами.</p><p>Но! Вы можете применить <a href="https://hackage.haskell.org/package/base-4.6.0.1/docs/Debug-Trace.html">Debug.Trace</a>. Данный прием схож с тем, как в Python функция печати не зависит от Haskell IO. И данный модуль может вначале принести пользу . Когда вы только начинали работать с Haskell, вы думали о том, как много вы будете его использовать?</p><p>Если вы не будете использовать операторы трассировки в вашем коде после проверки ошибок, то вы сразу заметите, что ваш код в Haskell выглядит хуже, чем в Python.</p><h3>Лучший учебник по монадам</h3><p>Каждый раз, когда вы слышите об успехе человека, программирующего на Haskell, вы задумываетесь: «Как этот человек до конца усвоил монады?» Итак, все по порядку.</p><p>Я должен провести синтаксический анализ. Я кое-что знал об этом, когда писал на Python. Но, в силу моей неопытности в этой области, сейчас сделать анализ достаточно сложно.</p><p>Окей, сейчас я расскажу подробнее. Я буду объяснять на Haskell.</p><p>Я нашел видео на YouTube, «<a href="https://youtu.be/r_Enynu_TV0">Parsing Stuff in Haskell</a>», в котором описывалось, как сделать JSON анализ в Haskell, используя при этом библиотеку Parsec.</p><p>Это видео помогло мне ненароком понять, как можно, используя монады и аппликативные функторы, создать то, что мне требовалось. Я понял, как их функции (har, har) соединяются друг с другом.</p><p>После написания синтаксического анализа при помощи видео, я начал понимать весь код. Я также начал понимать всю его «природу». Но на начальном этапе это не пригодится.</p><p>Итак, Parsec делает свое дело достаточно неплохо, так что моя неопытность при написании анализа практически не заметна. Так же, как и любой другой начинающий в Haskell, я с лёгкостью мог даже в начале знакомства с этим языком делать неплохие программы и операции.</p><h3>Польза от ваших знаний</h3><p>Haskell является моим основным языком по нескольким причинам:</p><ol><li>Выбор технологий, которыми я буду пользоваться.</li><li>Я могу писать свои программы быстрее, и чаще всего эти программы я и продаю.</li><li>Не приходится иметь дело с мелкими багами.</li><li>Даже сталкиваясь с несколькими ошибками, я быстро решаю их, так как они более-менее понятны.</li><li>Python не делал акцент на скорости работы. Haskell делает то же самое, но выбор все же за мной.</li><li>Рефакторинг, по сути, достаточно «ветреный». В Python я иногда сильно ругал себя, когда забывал править небольшие ошибки в коде, которые позже вызывали огромные затруднения.</li><li>Замечательные библиотеки. Основная особенность Haskell заключается в высоком качестве библиотек.</li><li><a href="https://gist.github.com/quchen/5280339">Сообщество</a>, всегда готовое помочь.</li><li>Простота масштабирования кода до нужного ядра.</li><li>Haskell часто обновляется. В прошлом году, когда GHC (компилятор для Haskell) был обновлен до версии 7.8, делая при этом написание кода в два раза удобнее, были ускорены многие веб-сервера.</li></ol><p>И в заключение хочу сказать, что Haskell доставил мне уйму удовольствия. Это был лучший опыт за всю мою жизнь.</p><h3>С чего можно начать?</h3><p>Я постарался и нашел много «отправных точек» для начала вашего знакомства с Haskell.</p><p>Здесь я продемонстрирую то, как бы я начинал, если бы был не знаком с Haskell.</p><p>Для начала, прочитайте статью «<a href="http://learnyouahaskell.com/chapters">Learn you a Haskell for Great Good</a>».</p><p>Отлично! Теперь выполните следующие пункты:</p><ol><li>Напишите маленький модуль, при этом не используя IO. Что-то на примере судоку, в котором происходит генерация пазлов. Используйте рандомные числа для начала. Используйте Debug. Используйте оператор печати, чтобы видеть каждый ваш шаг. Сделайте генерацию пазлов и Debug’а. Отправьте все на экран. Создайте свой собственный тип файлов и используйте функции.</li><li>Закиньте все на сайт. Для этого используйте <a href="http://adit.io/posts/2013-04-15-making-a-website-with-haskell.html">Scotty</a> или <a href="https://www.spock.li/tutorial/">Spock</a>. Вот и все, ваш URL готов. Затем, URL, <a href="https://www.schoolofhaskell.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/json">создающий JSON</a> в вашем пазле.</li><li>Повозитесь с IO. Попробуйте написать пазл в терминале, не пользуясь Debug.Trace.</li><li>Находите новые варианты. Измените формат файла для пазла судоку и напишите синтаксический анализ Parsec для него! Не делайте формат JSON — попробуйте что-то новое.</li></ol><p>Удачи!</p>]]></content:encoded>
    </item>
  </channel>
</rss>