<?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>Функциональное программирование</title>
    <description/>
    <link>https://tproger.ru/tag/functional-programming</link>
    <atom:link href="https://tproger.ru/tag/functional-programming/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sun, 25 Oct 2026 07:39:28 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Функциональное программирование</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Функциональные флаги в CI/CD: что, зачем и как</title>
      <link>https://tproger.ru/articles/funkcionalnye-flagi-v-ci-cd-chto-zachem-i-kak</link>
      <comments>https://tproger.ru/articles/funkcionalnye-flagi-v-ci-cd-chto-zachem-i-kak?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Лукьянов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/funkcionalnye-flagi-v-ci-cd-chto-zachem-i-kak</guid>
      <description><![CDATA[<p>Что такое функциональные флаги и как они ускоряют CI/CD. Виды флагов, инструменты (Flagsmith, LaunchDarkly), интеграция с pytest и лучшие практики внедрения.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/funkcionalnye-flagi-v-ci-cd-chto-zachem-i-kak">Функциональные флаги в CI/CD: что, зачем и как</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Бэкенд]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 07 Oct 2026 12:02:39 GMT</pubDate>
      <content:encoded><![CDATA[<p>Собрали небольшой гайд по функциональным флагам: какими они бывают, чем полезны, зачем нужны в CI/CD и как их правильно использовать.</p><p>Функциональные флаги (feature flags, feature toggles) позволяют разработчикам включать и отключать функции приложения, но при этом не требуют повторно собирать и развёртывать код. Благодаря флагам в CI/CD можно чётко отделить друг от друга этапы «развёртывания» (доставки результатов разработки и сборки) и «релиза» (запуска новой функциональности для пользователей). Для чего и как именно это делается — разберём в этой статье.</p><h2>Что такое функциональный флаг</h2><p>Технически функциональный флаг — это специальный код в приложении, который проверяет значение предварительно выбранной переменной. В зависимости от этого активируется разная логика.</p><p>Ниже приведён пример JSON, который получает приложение. Оно проверяет геотег пользователя, и если пользователь из Москвы, то значение флага возвращает true и в итоге отображается виджет погоды. Для пользователей из других городов значение флага принимает default_variant: "disabled", и виджет для них не показывается.</p><p>Функциональные флаги могут использоваться в разных сценариях — приведём основные с парой примеров:</p><ul><li>Повышение надёжности системы: отключение рекомендаций при высокой нагрузке, переключение на резервный платёжный шлюз при сбоях в основном.</li><li>Обучение через продуктовые эксперименты и A/B-тестирование: сравнение двух вариантов страницы регистрации, тестирование двух видов кнопки «Купить».</li><li>Управление доступом по тарифам и подпискам: экспорт отчётов только для пользователей Pro, расширенные настройки только для корпоративных клиентов.</li><li>Сезонные и временные функции: промокоды на «чёрную пятницу», приём работ для конкурса.</li><li>Аварийные выключатели (kill switches): отключение медиаплеера из-за ошибок, удаление неверно настроенного способа оплаты.</li></ul><p>Далее рассмотрим основные типы флагов.</p><h2>Типы флагов</h2><p>Флаги классифицируются по механизму работы и целевому назначению.</p><h3>По механизму работы: статические и динамические флаги</h3><p><b>Статические флаги</b> чаще всего используют, чтобы скрыть недоделанную функциональность до релиза, проставить внутренние флаги, которые не требуют частого переключения, или флаги в микросервисах с редкими развёртываниями.</p><p>Их значение фиксируется на этапе сборки или при старте приложения. Чтобы изменить значение такого флага, придётся перезапускать сервис.</p><p>Статические флаги задаются через переменные окружения (.env), конфиг-файлы (config.json) или внешние сервисы конфигурации (Consul).</p><p>Пример в .env:</p><p>Пример в виде конфиг-файла:</p><p>Использование в коде:</p><p>Плюсы: простота, нулевые сетевые задержки, предсказуемость, безопасность (нет внешних вызовов в runtime).</p><p>Минусы: невозможность изменить поведение «на лету» — требуется пересборка/перезапуск.</p><p><b>Динамические флаги</b> идеальны для экспериментов с пользователями, постепенного развёртывания и таргетинга по регионам или ролям. Их можно менять в runtime без перезапуска приложения.</p><p>Такие флаги управляются через внешние системы: API, БД, специализированные сервисы.</p><p>В примере ниже — два флага. Первый проверяет, попадает ли пользователь в сегмент для тестирования нового способа оформления заказа: берутся только 25 % пользователей сервиса; также они должны входить в группы "beta_testers", "premium_users" и быть из России, Беларуси или Казахстана. Второй флаг показывает, что ИИ-рекомендации при заказах пока отключены для всех пользователей, но в нужный момент это можно изменить даже во время работы приложения.</p><p>Пример конфигурации:</p><p>Использование в коде:</p><p>Плюсы: гибкость — можно включать и выключать фичи на лету, перенастраивать систему без перезапуска сервиса (критично для высоконагруженных систем).</p><p>Минусы: усложнение логики системы (flag debt), дополнительная задержка при запросе состояния флага и зависимость от внешних систем.</p><p>Сведём сравнение типов флагов в небольшую таблицу:</p><figure><img src="https://media.tproger.ru/user-uploads/133989/2026-10-07/c805be74-aee7-4c16-9722-b241cf817d1b.webp" alt="" /></figure><h3>По цели применения: релизные, экспериментальные, операционные и разрешающие флаги</h3><p><b>Релизные флаги</b> (release flags) позволяют отделить развёртывание от релиза, то есть экспериментальная/незаконченная фича вливается в основную ветку и развёртывается вместе с остальным кодом, но остаётся выключенной до официального релиза. Если после релиза что-то пойдёт не так, флаг можно сразу же отключить, не делая новый релиз и не откатывая приложение. Когда новая функциональность начинает работать стабильно, релизный флаг удаляют.</p><p>Например, код нового платёжного шлюза уже в продакшене, но флаг пока enabled: false. Флаг включат 1 ноября, и фича станет доступной всем. Если с платежами возникнут проблемы, флаг сразу же отключат без отката деплоя.</p><p><b>Экспериментальные флаги</b> (experiment flags) — это инструмент для A/B-тестов и проверки гипотез. Они позволяют показать разные новые варианты новой функции разным пользователям (или показать новую функциональность только ограниченной лояльной группе пользователей), чтобы изучить их реакцию и на её основе сделать вывод о новой фиче. По окончании A/B-теста экспериментальный код и флаг удаляют.</p><p>В примере ниже команда интернет-магазина проводит тест, чтобы выбрать подходящий цвет для кнопки «Оформить заказ» в корзине. Выборка из 30 % пользователей распределяется случайным образом: 50 % из них видят зелёную кнопку, 25 % — красную, 25 % — оранжевую. Система собирает метрики: конверсию и отказ от корзины. Через две недели становится ясно, что оранжевая кнопка даёт +15 % к конверсии, поэтому её делают основной, а флаг и экспериментальный код удаляют.</p><p><b>Операционные флаги</b> (ops flags) нужны, чтобы регулировать поведение системы. С их помощью можно отключать ресурсоёмкие операции во время больших нагрузок или на слабых устройствах, переключаться между внешними сервисами, предотвращать критические сбои при запуске новой функциональности, управлять режимами деградации, проводить диагностику при инцидентах.</p><p>Операционные флаги зачастую не удаляют и оставляют в системе.</p><p>Рассмотрим следующий пример. В «чёрную пятницу» нагрузка в приложении интернет магазина растёт, утилизация CPU превышает 85 %. Из-за этого отключаются ИИ-рекомендации (на ресурсоёмкой ML-модели) и включаются обычные статические. После того как нагрузка снижается, флаг возвращается в исходное состояние.</p><p>Второй флаг позволяет переключиться на режим обслуживания БД, а третий — на резервный платёжный сервис, если есть сбои в основном.</p><p><b>Разрешающие флаги</b> (permission flags) — это одна из составляющих настройки доступа к специфическим функциям приложения, например к ролям в админ-панели для разработчиков и менеджеров или к платным функциям для клиентов. Такие флаги могут быть очень динамичными и менять состояние при каждом запросе.</p><p>Разрешающие флаги обычно долгоживущие и становятся неотъемлемой частью жизненного цикла приложения.</p><p>В примере ниже при каждом запросе к админ-панели система проверяет роль пользователя и статус подписки. Администратор видит все разделы, менеджер — только ограниченный набор, а премиум-клиент получает доступ к дашборду аналитики. Администраторы и премиум-клиенты также могут экспортировать отчёты в формате Excel.</p><p>Далее рассмотрим, какие существуют инструменты для управления функциональными флагами.</p><h2>Системы управления флагами</h2><p>Глобально все решения для управления флагами делятся на три категории: проприетарные, Open Source и собственные разработки.</p><p>Проприетарные SaaS-платформы предлагают готовую инфраструктуру, поддержку и продвинутый функционал «из коробки». Они идеально подходят для команд, которые хотят сосредоточиться на продукте, а не на поддержке инструмента. Крупным компаниям стоит инвестировать именно в такие решения, а для стартапов могут подойти базовые тарифы.</p><p>Перечислим тут несколько самых известных проприетарных инструментов для управления флагами:</p><ul><li>LaunchDarkly позволяет включать функционал для конкретных сегментов аудитории с высокой гранулярностью, то есть подходит для детального таргетинга пользователей и маркетинговых экспериментов. Платформа даёт enterprise-уровень надёжности и позволяет гибко управлять жизненным циклом флагов.</li><li>ConfigCat обеспечивает единую точку управления, если инфраструктура состоит из разнородных технологий (веб, мобильные приложения, бэкенд на разных языках).</li><li>Harness интегрирует управление флагами прямо в CI/CD-процессы. Предоставляет командам детальную аналитику о влиянии флагов на производительность и стабильность системы прямо в пайплайне развёртывания.</li></ul><p>Open Source-решения дают полный контроль над кодом и данными и позволяют развёртывать систему на своих серверах (on-premise), что критично для организаций со строгими требованиями к безопасности.</p><p>Вот пара примеров Open Source-платформ для управления флагами:</p><ul><li>Flagsmith позволяет сохранить данные внутри периметра компании, хорошо поддаётся конфигурации и кастомизации через удобный интерфейс.</li><li>Unleash хорошо подходит компаниям, которым важно разграничивать права управления флагами между разными командами, менеджерами и разработчиками.</li></ul><p>Собственная разработка тоже возможна, но требует значительных ресурсов. На ранних этапах разработки продукта или при тестировании гипотез внедрения флагов можно использовать переменные окружения, JSON-файлы или простой API-сервис. Но по мере роста продукта поддерживать собственное решение становится всё сложнее: нужно обеспечивать отказоустойчивость, безопасность и разработку SDK для всех языков программирования в проекте. Так что переход на специализированный инструмент (Open Source или SaaS) — обычно вопрос времени.</p><p>Независимо от того, какое решение вы выберете, важно убедиться, что оно легко интегрируется с вашей CI/CD-системой и не создаёт узких мест в разработке. Кроме того, внедрять полноценную систему управления флагами на ранних этапах разработки может быть избыточным, поэтому нужно всегда спрашивать себя, стоят ли затраченные усилия желаемого результата.</p><p>Теперь посмотрим, какую роль функциональные флаги играют в CI/CD.</p><h2>Функциональные флаги в CI/CD</h2><p>Флаги фундаментально меняют подход к управлению исходным кодом, особенно если используется модель ветвления Trunk Based Development (TBD).</p><p>При TBD в основную ветку (main/trunk) часто вливаются изменения. Функциональные флаги позволяют разработчикам интегрировать и развёртывать фичи, которые ещё не готовы к релизу, и при этом не рисковать стабильностью всего приложения. Благодаря флагам быстрее внедряются изменения и упрощается слияние, так как весь код (и основной, и разрабатываемый) остаётся в одной ветке. Это предотвращает конфликты слияния (merge hell), которые часто случаются при долгоживущих ветках.</p><p>Флаги улучшают управление рисками, а именно:</p><ul><li>дают возможность выключить функциональность сразу после релиза, если она работает неправильно, — благодаря этому система быстрее восстанавливается (MTTR);</li><li>позволяют включать фичу поэтапно, делать canary/rollout, быстро откатывать поведение через переключатель и не ждать новой сборки для исправления проблемы.</li></ul><p>На сам CI/CD-пайплайн функциональные флаги никак не влияют. Основная задача пайплайна — запуск автотестов, сохранение результатов их работы и передача далее для дальнейшего анализа. Проверка функциональности при включённом и выключенном флаге должна быть частью автотестов.</p><p>Чтобы эффективно протестировать оба состояния флага, можно использовать параметризованные тесты (переключаться между состояниями внутри одного набора тестов) либо поддерживать отдельные наборы тестов для включённого и выключенного флага.</p><p>Параметризованные тесты позволяют динамически переключать состояние флага во время выполнения приложения и тем самым уменьшают дублирование кода.</p><p>Отдельные наборы тестов могут быть полезны, когда состояния флагов фиксированы в разных окружениях (например, staging и production) или когда предпочтительнее запускать тесты по отдельности.</p><p>Допустим, мы готовим новую версию приложения и до того, как выпустить её в продакшен, тестируем релиз-кандидата в окружении staging. CI/CD-пайплайн собрал образ release-candidate и развернул его в staging. При запуске приложения запрашивается набор флагов из централизованной системы управления флагами. Далее при запуске автотестов подбирается набор тест-кейсов для заданного окружения. Также подбирается набор флагов для конкретного пользователя после того, как его логин и user id считаются из cookie во время запроса.</p><p>В результате тестовые сценарии становятся более гибкими, позволяют прогонять тесты в разных режимах флага, а также отдельно проверять критичные сценарии и постепенно расширять набор проверок.</p><p>Ниже подробнее рассмотрим сценарий тестирования релиз-кандидата.</p><h2>Сценарий тестирования релиз-кандидата с применением функциональных флагов</h2><p>Сценарий состоит из следующих шагов:</p><ol><li>CI/CD-пайплайн собирает образ release-candidate и деплоит в staging.</li><li>При запуске приложения запрашиваются флаги из Flagsmith с параметрами environment=staging, user_id, role.</li><li>Система флагов возвращает конфигурацию: какие флаги включены и какие тест-кейсы нужно выполнить.</li><li>Автотесты динамически фильтруют тесты на основе полученного ответа. Тесты, которые не относятся к активным функциям текущего окружения, не запускаются.</li></ol><p>Все флаги хранятся в системе управления флагами Flagsmith в формате JSON.</p><p>Здесь описан флаг new_payment_flow — он управляет новой версией процесса оплаты.</p><p>В нашем примере функция включена в staging и выключена в production.</p><p>Помимо самого состояния флага, конфигурация содержит список тестов для каждого окружения. Для staging предусмотрены три проверки:</p><ul><li>успешная оплата;</li><li>обработка ошибки;</li><li>обработка тайм-аута.</li></ul><p>Для production пока оставлен только тест успешной оплаты.</p><p>В config находятся параметры, которые тесты также могут получить из системы флагов. В нашем случае это timeout_ms 5000. Значение тайм-аута не зашито непосредственно в тесте, и его можно изменить через конфигурацию флага.</p><p>Автотесты при запуске запрашивают флаги из внешней системы с помощью следующего запроса:</p><p>Здесь тестовый раннер сообщает системе флагов контекст, в котором выполняется проверка. В параметре context указаны идентификатор release candidate и источник запроса — autotest_runner.</p><p>Получив такой запрос, система флагов определит, какие настройки применимы именно к этому запуску.</p><p>Автотесты используют функциональные флаги, чтобы динамически сформировать определённый тестовый сценарий. Для этого процесс сбора тестов перехватывается через специальную функцию (conftest.py):</p><p>Тут pytest находит все тесты, какие есть. Код узнаёт, в каком окружении запускаются тесты, подключается к Flagsmith и получает активные флаги.</p><p>Из флагов берётся список тестов, которые нужно запустить для конкретного окружения. Из всех найденных тестов остаются только те, которые:</p><ul><li>относятся к нужному окружению;</li><li>указаны в конфигурации активного флага.</li></ul><p>В результате pytest запустит только оставшиеся тесты.</p><p>Функциональные флаги влияют не только на выбор тестов. Сам тест также может получать из флага параметры, необходимые для его выполнения:</p><p>Здесь из конфигурации флага new_payment_flow извлекается параметр timeout_ms. В нашей конфигурации его значение было задано — 5000. Если бы оно не было указано, использовалось бы значение по умолчанию — 3000.</p><p>Таким образом, CI/CD-пайплайн остаётся универсальным и не содержит отдельной логики вроде «если это такой-то релиз, запусти такие-то тесты». Он запускает один и тот же механизм, а конкретное поведение определяется конфигурацией функциональных флагов.</p><p>Итого мы получаем инструмент, который позволяет гибко управлять тем, какие тесты и в каком окружении запускать и для каких пользователей включать новую функциональность.</p><h2>Риски и рекомендации по использованию флагов</h2><p>Флаги — это мощный инструмент, но если неправильно их использовать, они могут принести проекту больше вреда, чем пользы, поэтому применять их нужно с осторожностью.</p><h3>Основные риски при работе с флагами</h3><p><b>Деградация кода и тестов</b>. Забытые флаги создают в коде множество ветвлений. В результате тестировать такой код становится сложнее: нужно проверять не только каждый флаг отдельно, но и разные сочетания флагов.</p><p><b>Проблемы производительности и доступности</b>. Постоянные сетевые запросы к сервису флагов замедляют работу приложения, если не используется кеширование. Кроме того, сервис флагов становится критической зависимостью: если он упадёт, вместе с ним могут упасть все зависимые микросервисы.</p><p><b>Рассинхронизация</b>. Разные экземпляры приложения могут узнать об изменении флага не одновременно, поэтому могут временно работать по-разному.</p><h3>Архитектурные рекомендации</h3><p>Прежде всего, флаги нужно вовремя удалять: можно настроить в CI/CD автоматические уведомления на флаги, которые живут дольше установленного времени, например больше 30 дней. А релизные и экспериментальные флаги лучше удалять сразу после того, как стабилизировалась фича или закончился A/B-тест. У каждого флага должны быть владелец, дата создания и план удаления.</p><p>Как мы уже писали выше, флаги — это критическая зависимость, поэтому стоит обезопасить себя от сбоев в системе управления флагами: использовать на стороне приложения fallback-значения (default variants), внедрить локальное кеширование (чтобы пережить кратковременные падения сети), использовать автоотключение (Circuit Breaker) запросов к сервису флагов при множественных таймаутах.</p><p>Обезопасить себя при работе с флагами нужно и с других сторон: нельзя хранить в их конфигурации секреты или персональные данные, необходимо логировать все изменения (кто, когда и что изменил) и внедрить обязательный code review и approval-процесс для критичных флагов.</p><p>Чтобы избежать взрыва матрицы тестов, не нужно создавать отдельные тесты для каждого состояния флага — лучше использовать параметризованные тесты и динамическую фильтрацию (вроде того сценария, что мы рассматривали выше).</p><p>В целом, в зрелой практике флаги используют как временный инструмент для поставки и экспериментов, а не как постоянная замена нормальной архитектуре.</p><p>Резюмируем ключевые принципы:</p><ul><li>Временность. Для флагов должен быть чёткий план удаления.</li><li>Прозрачность. У каждого флага должны быть владелец, назначение и срок жизни.</li><li>Отказоустойчивость. Приложение должно работать даже при недоступности сервиса управления флагами.</li><li>Мониторинг. Состояние и использование флагов нужно постоянно отслеживать.</li></ul><p>Итак, функциональные флаги — это важный элемент культуры непрерывной поставки. Они повышают гибкость, безопасность и скорость доставки ПО, позволяют командам выпускать код чаще. Но важно помнить, что флаги — это не панацея: они дают максимум пользы только при грамотном внедрении и регулярной очистке, а без должной дисциплины могут усложнить кодовую базу и тестирование.</p>]]></content:encoded>
    </item>
    <item>
      <title>Разработка TypeScript-библиотеки для построения реактивных графов распространения и обработки данных</title>
      <link>https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo</link>
      <comments>https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сморен Фрилайт]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo</guid>
      <description><![CDATA[<p>Как построить гибкий реактивный граф обработки данных, где узлы изолированы друг от друга, а связи между ними строятся автоматически на основе их возможностей? Рассказываю о разработке Transferum — легковесной TypeScript-библиотеки для реактивных потоков. Внутри: разбор системы вычислимых типов для проверки контрактов в compile-time, управление маршрутизацией данных в графе и примеры построения динамических пайплайнов для IoT-датчиков и панелей управления.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo">Разработка TypeScript-библиотеки для построения реактивных графов распространения и обработки данных</a>»</p>]]></description>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 23 Jul 2026 15:14:02 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Предыстория</h2><p>Все началось с рабочей задачи по реализации весьма специфичной web-панели мониторинга и управления различным оборудованием, в которой нужно было получать данные и отправлять команды по разнообразным сценариям (периодический опрос, подписка на websocket-события, https-запросы и т.д.), а также выводить состояние и графики в реальном времени, динамически комбинируя различные источники данных в разных виджетах.</p><p>Кроме того, у панели было предусмотрено несколько настраиваемых режимов работы, которые переключались по запросу пользователя, что требовало массового управления маршрутами потоков данных внутри системы.</p><p>В первой итерации с использованием RxJS получилось много неструктурированного и сложно поддерживаемого кода, так как архитектура на RxJS вынуждала императивно описывать перестроение топологии при изменении правил маршрутизации «на лету». В нашем специфичном кейсе с динамическими виджетами это приводило к сайд-эффектам и сложностям в отладке. Кроме того, местами размывалась строгая типизация и мы лишались compile-time гарантий. Так появилась идея разработать собственное решение на основе альтернативной концепции — модели графа потоков данных.</p><p>В результате получившаяся модель продемонстрировала предсказуемое поведение и низкую связность компонентов, а кодовая база сократилась на ~30% и приобрела более декларативный вид. Убедившись в эффективности решения, мы решили оформить его в виде отдельной библиотеки с открытым исходным кодом — <b>Transferum</b>.</p><h2>И что, получилась просто еще одна реактивная библиотека?</h2><p>Не совсем. Классические FRP-библиотеки (RxJS, Bacon, Most) построены вокруг единственного примитива (Observable<i></i>). Transferum же основан на композиции различных типов узлов с явно определенным поведением.</p><p>Каждый узел в графе потоков распространения данных явно декларирует свои способности: может ли он принимать данные через push, отдавать через pull, распространять полученный сигнал подписчикам, опрашивать источник, фильтровать, блокировать поток и т.д.</p><p>Объявленные узлом возможности являются одновременно <b>флагами для использования в runtime</b> и <b>compile-time гарантиями</b> наличия соответствующих методов, определяющих его поведение.</p><p><b>Ключевая идея:</b> поведение системы описывается как композиция независимых возможностей, которые одновременно определяют тип, реализацию и правила взаимодействия.</p><h2>Transferum предоставляет четыре слоя абстракции:</h2><ol><li>Трансферы — узлы графа (каналы, поллеры, мапперы, буферы, разветвители и концентраторы, реализации debounce, throttle, switchMap и т.д.).</li><li>Мосты — ребра графа — управляемые вентили между узлами с динамической маршрутизацией и гейтингом.</li><li>Операторы — stateless-трансформаторы и фильтры данных (используются трансферами, отвечающими за конвертацию данных).</li><li>Билдеры — fluent-конструкторы композитных трансферов из цепочек трансферов-примитивов.</li></ol><p>Концептуальная и архитектурная основа — <b>capability flags system</b>. Каждый трансфер реализует CommunicationContractInterface<i></i> — набор булевых флагов, определяющих его возможности.</p><p>Флаги isPushable, isPullable, isSubscribable, isGate и другие — это не просто свойства объекта. Это метаданные, которые:</p><ul><li>Определяют TypeScript-интерфейс трансфера на этапе компиляции.</li><li>Управляют стратегией связывания с другими трансферами в рантайме (с помощью функции linkTransfers()).</li><li>Обеспечивают совместимость в билдерах без приведений типов.</li></ul><p>Один набор флагов — три потребителя. Это единый источник истины для всей системы.</p><p>Когда флаг равен true, соответствующий метод входит в TypeScript-интерфейс трансфера. Это позволяет предоставлять трансфер пользователю вот так:</p><p>Или вот так:</p><p>Именно в таком формате типов фабрики в библиотеке возвращают трансферы. Например:</p><p>Эта <i>«магия»</i> работает в compile-time благодаря несколько замысловатой системе вычислимых типов:</p><h2>Архитектурные инварианты</h2><h2>1. Трансферы не знают своих соседей</h2><p>Трансфер определяет своё поведение (push, pull, subscribe и др.), но никогда не ссылается и не проверяет класс другого трансфера. Он не знает, что является upstream или downstream — лишь выполняет свой контракт. Пользователь может создать свой трансфер, объявить и реализовать его возможности — и он органично и бесшовно впишется в экосистему.</p><h2>2. Мосты не знают конкретных реализаций</h2><p>Мост инспектирует capability flags, а не имена классов. Нет цепочки instanceof, нет переключения по имени класса. Любой output-трансфер может быть соединен с любым input-трансфером — при условии совместимости их флагов, о чем мы поговорим чуть ниже. Это применимо и к тем узлам, которые еще не существуют и будут созданы пользователем.</p><h2>3. Значение undefined никогда не распространяется</h2><p>В Transferum undefined означает «нет данных», а не «пустое значение». Оно подавляется на уровне внутренней реализации менеджера подписок — подписчики никогда не уведомляются с undefined. При этом для явных маркеров пустых значений можно использовать null. <i>Мы сознательно пошли на этот компромисс, чтобы избежать runtime-оверхеда и сохранить нативную скорость работы на плотных потоках данных.</i></p><h2>Связывание трансферов</h2><p>Функция linkTransfers(lhs, rhs) соединяет output-трансфер (lhs) с input-трансфером (rhs) с автоматическим выбором стратегии связывания на основе возможностей этих трансферов:</p><ul><li>isSubscribable → isPushable (реактивная подписка);</li><li>isPullable → isPollingProxy (активный опрос);</li><li>isSubscribable → isAsyncPushable (реактивная подписка + асинхронный push);</li><li>isAsyncPullable → isAsyncPollingProxy (активный асинхронный опрос асинхронного pull-источника);</li><li>isPullable → isAsyncPollingProxy (активный асинхронный опрос синхронного pull-источника).</li></ul><p><b>Protocol-oriented design:</b> механизм не спрашивает <b>«какой это класс?»</b> — он выясняет, <b>какие у него есть возможности</b>. Любая пара трансферов с совместимыми возможностями является <b>linkable</b>. Добавление нового класса трансфера требует только объявления его флагов и реализации соответствующих методов — как связать его с другим трансфером, связующий алгоритм разберется сам.</p><h2>Sync и async в одной экосистеме</h2><p>Синхронные и асинхронные трансферы сосуществуют и могут быть связаны между собой. linkTransfers() предпочитает sync-связывание, когда это возможно, а async-стратегии применяет только когда sync неприменим. Нет отдельного «асинхронного мира».</p><h2>Поддержка backpressure</h2><p>Ряд асинхронных трансферов (AsyncSinkTransfer, AsyncWriteTransfer, AsyncConvertTransfer, AsyncConditionTransfer) поддерживают необязательные поля в конфигурации: maxConcurrency, bufferSize и onBufferOverflow — для ограничения параллельных async-операций, очереди избыточных данных и graceful-обработки переполнения. По умолчанию — неограниченная обработка, без буферизации.</p><h2>Локальная обработка ошибок</h2><p>Transferum использует единую модель обработки ошибок для всех трансферов. Каждый трансфер, который может столкнуться с runtime-ошибкой, принимает опциональный onError-хэндлер в своей конфигурации.</p><h2>А теперь — к примерам использования</h2><p>Вот так можно просто и декларативно описать опрос и агрегирование данных из нескольких источников:</p><p>А вот как можно организовать динамический роутинг:</p><p>Пример организации игровой механики:</p><h2>Когда имеет смысл попробовать Transferum</h2><p>Библиотека подойдет для:</p><ul><li>TypeScript-first проектов — благодаря максимально строгой типизации и compile-time вычислению доступных методов любого трансфера на основе объявленных у него флагов возможностей.</li><li>Работы с pull-based источниками данных — polling API, датчиков, хранилищ с PollingProxy.</li><li>Смешанных sync/async пайплайнов — в единой модели без ручного преобразования.</li><li>Явного flow control — gates, bridges, selectors для runtime-маршрутизации.</li><li>Game development / IoT — frame-aligned tickers, idle polling, sensor aggregation.</li><li>Устойчивой обработки ошибок — локальная, non-fatal обработка: одна стадия не убивает пайплайн при условии переданного в конфиге обработчика ошибок, ничего не подавляется молча.</li></ul><h2>Результаты и планы</h2><ul><li>Библиотека уже используется в двух наших внутренних проектах и показывает свою эффективность. Код доступен на GitHub под лицензией MIT, библиотека не имеет внешних зависимостей и поставляется с подробной документацией (README + API Reference).</li><li>В одном из проектов граф состоит из ~80 узлов и стабильно обрабатывает несколько сотен событий в секунду без деградации. На основе этих данных в том числе рендерится 3D-сцена в Babylon.js со стабильным фреймрейтом ~60 FPS без микрофризов.</li><li>Тесты библиотеки покрывают не только отдельные трансферы, но и поведение системы в динамике: переподключение мостов, обработку ошибок в длинных асинхронных цепочках, а также разнообразные граничные случаи. Покрытие — 100%.</li><li>В дальнейшем планируем реализовать хуки и утилиты для более удобного и нативного использования Transferum с Vue и React. Если они окажутся в достаточной мере переиспользуемыми, оформим в отдельные пакеты-адаптеры.</li></ul><p>Буду рад, если вы заглянете в <a href="https://github.com/Smoren/transferum-ts" rel="noopener noreferrer nofollow">репозиторий</a>, попробуете библиотеку в деле и поделитесь замечаниями — обратная связь поможет сделать Transferum лучше.</p><p>P. S. Если вы сталкивались с похожими задачами и решили их как-то иначе — буду рад прочитать о вашем опыте в комментариях.</p>]]></content:encoded>
    </item>
    <item>
      <title>itertools в Python: ленивые итераторы без лишних циклов</title>
      <link>https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov</link>
      <comments>https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov</guid>
      <description><![CDATA[<p>Разбираем модуль itertools из стандартной библиотеки Python: как ленивые итераторы экономят память, какие функции использовать чаще всего и где поджидают подводные камни.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/itertools-v-python-lenivye-iteratory-bez-liwnih-ciklov">itertools в Python: ленивые итераторы без лишних циклов</a>»</p>]]></description>
      <category><![CDATA[Алгоритмы и структуры данных]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Практика]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 12 Jul 2026 17:20:36 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если ваш код регулярно превращается в пять вложенных for и огромный список, который держится в памяти только ради того, чтобы тут же быть выброшенным в мусор, — скорее всего, вам не хватает itertools. Этот модуль стандартной библиотеки Python собрал ленивые итераторы, которые превращают обработку данных в конвейер: элементы поступают, преобразуются и уходят дальше без лишних промежуточных коллекций.</p><p>itertools — это не просто набор функций, а способ мыслить о данных как о потоке. Вместо того чтобы сначала строить список, а потом его перебирать, вы описываете, что должно произойти с каждым элементом, и получаете результат по мере необходимости. В статье разберём три семейства инструментов, покажем рабочие примеры и укажем на типичные ловушки.</p><p>itertools — модуль стандартной библиотеки Python, который предоставляет ленивые итераторы для композиции цепочек обработки данных.</p><p>Ленивость означает, что элементы создаются по запросу, поэтому можно работать с большими файлами и бесконечными потоками, не загружая всё в память.</p><p>Функции разделены на три группы: бесконечные итераторы (count, cycle, repeat), конечные итераторы (chain, islice, groupby и др.) и комбинаторные (product, permutations, combinations).</p><p>Многие рутинные задачи — flatten, батчинг, скользящее окно, группировка — решаются в одну строку, если знать правильную комбинацию функций.</p><p>Подводные камни: groupby требует предварительной сортировки по тому же ключу; итераторы одноразовые; tee() может неэкономно расходовать память.</p><h2>Зачем вообще ленивые итераторы?</h2><p>Обычный подход в Python — сгенерировать список и пройтись по нему циклом. Это просто и читаемо, пока данных немного. Когда файл весит несколько гигабайт, а строки приходят из сети, список становится дорогим: он требует памяти и времени, хотя в каждый момент вам нужен лишь один текущий элемент.</p><p>Ленивый итератор не строит коллекцию целиком. Он помнит текущее состояние и умеет «добыть» следующий элемент. Поэтому itertools работает с любыми итерируемыми источниками — файлами, генераторами, сетевыми потоками — и не требует, чтобы весь объём данных поместился в оперативную память.</p><p><b>Память vs скорость:</b><br />Ленивость экономит память, но не всегда ускоряет код. Если данные всё равно нужны целиком — например, для сортировки — список может оказаться быстрее. Используйте итераторы там, где важна потоковая обработка.</p><h2>Три семейства инструментов</h2><p>Документация Python делит функции модуля на три группы. Такое разделение помогает быстро выбрать инструмент: нужна бесконечная последовательность, преобразование конечной или перебор комбинаций.</p><h3>Бесконечные итераторы: count, cycle, repeat</h3><p>count — это range без конца. Ему можно задать начальное значение и шаг, и он будет выдавать числа до тех пор, пока его не остановят снаружи. cycle бесконечно повторяет переданную последовательность, а repeat — бесконечно или заданное число раз возвращает один объект.</p><p>Классический трюк — сочетание map и count: map(f, count()) работает как математическое табулирование tabulate(f), знакомое из SML и Haskell.</p><h3>Конечные итераторы: chain, islice, groupby и другие</h3><p>Это самая многолюдная группа. Здесь есть функции для склейки последовательностей, фильтрации, нарезки, группировки и пакетной обработки. Их объединяет одно: на вход подаётся конечный или контролируемый итератор, на выходе — тоже итератор.</p><p>batched появился в Python 3.12 и сразу стал незаменимым инструментом: партии запросов к API, пакеты строк для вставки в базу, страницы данных. Последняя партия может быть короче — это поведение по умолчанию.</p><p>groupby часто путают с SQL-аналогом, но это не тот же инструмент. Python-версия группирует только подряд идущие одинаковые ключи, поэтому перед ней обычно нужна сортировка по тому же ключу. Если забыть про это, результат покажется случайным.</p><h3>Комбинаторные итераторы: product, permutations, combinations</h3><p>Эти функции генерируют декартово произведение, перестановки и сочетания. Они незаменимы в тестировании, алгоритмах на графах, задачах оптимизации и даже в простых играх. Все они ленивые, поэтому можно перебирать комбинации по одной, не строя гигантский список.</p><p>product с аргументом repeat удобен для перебора многомерных конфигураций: product([0, 1], repeat=3) даст все двоичные triples, как в таблице истинности.</p><h2>Подводные камни, за которые хватаются новички</h2><p>Несмотря на простоту отдельных функций, у модуля есть несколько особенностей, которые легко превратить в баг.</p><ul><li>groupby работает только с подряд идущими одинаковыми ключами. Перед вызовом сортируйте данные по тому же ключу, иначе группы разобьются.</li><li>Итераторы одноразовые. После list(iterator) исходный итератор опустошён, и второй проход по нему даст пустой результат.</li><li>tee копирует данные во внутренний буфер, пока все производные итераторы не прочитают их. Если один итератор сильно отстаёт, память может расти не хуже списка.</li><li>zip_longest с бесконечным итератором никогда не остановится. Ограничивайте такие комбинации islice или takewhile.</li><li>product полностью потребляет входные итераторы, чтобы построить пулы значений. С бесконечными последовательностями его использовать нельзя.</li></ul><h2>Рецепты: от простого к составному</h2><p>Документация Python включает раздел рецептов — готовые комбинации функций, которые решают частые задачи. Некоторые из них настолько удобны, что со временем превращаются в полноценные функции модуля: так появились accumulate, compress и pairwise.</p><p>Для sliding_window сейчас часто используют рецепт из документации или аналог из more-itertools, где функция уже реализована и хорошо протестирована.</p><h2>Выводы</h2><p>itertools — это не библиотека для красивых однострочников, а инструмент для правильного мышления о данных. Он помогает отказаться от лишних промежуточных списков, писать компактные конвейеры и работать с потоками, которые не помещаются в память. Главное — не гнаться за краткостью любой ценой: иногда явный цикл понятнее, чем цепочка из пяти функций.</p><blockquote>Together, they form an iterator algebra making it possible to construct specialized tools succinctly and efficiently in pure Python.</blockquote><p>Источник: <a href="https://docs.python.org/3/library/itertools.html">itertools — Functions creating iterators for efficient looping</a>. Если в вашем коде до сих пор царят вложенные циклы и огромные списки — попробуйте заменить их на поток. Скорее всего, получится короче, быстрее и понятнее.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как TypeScript выводит типы переменных: разбор алгоритма</title>
      <link>https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma</link>
      <comments>https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma</guid>
      <description><![CDATA[<p>Разбираем двухфазный алгоритм вывода типовых переменных в TypeScript: сбор кандидатов, разрешение, вариантность, пересечения и NoInfer. Узнайте, почему компилятор ведёт себя именно так.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-typescript-vyvodit-tipy-peremennyh-razbor-algoritma">Как TypeScript выводит типы переменных: разбор алгоритма</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Компиляторы]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 08 Jun 2026 05:15:55 GMT</pubDate>
      <content:encoded><![CDATA[<p>Если вы когда-нибудь ловили себя на мысли, что TypeScript ведёт себя странно с дженериками, знайте: вы не одиноки. TypeScript выводит типовые переменные в две фазы: сначала собирает кандидатов из аргументов и контекста, затем сворачивает список в единственный тип. Иногда компилятор выводит unknown там, где ожидаешь конкретный тип, а иногда — наоборот, слишком поспешно захватывает весь объект вместо его «остаточной» части. В этой статье разберём, как внутри устроен алгоритм вывода типовых переменных — и почему он ведёт себя именно так.</p><p>Это не дословный перевод, а авторский разбор на основе исследования Nicolas Laurent (norswap), дополненный практическими примерами для enterprise-разработки на TypeScript.</p><h2>Что такое вывод типовых переменных</h2><p>В TypeScript, когда вы вызываете функцию с дженериком function foo&lt;T&gt;(x: T), компилятор должен понять, чему равен T. Этот процесс называется <b>type variable inference</b> — выводом типовой переменной. Он происходит в две фазы: сначала TypeScript собирает «кандидатов» из типов аргументов и контекста возврата, а затем «сворачивает» список кандидатов в единственный тип.</p><p>Звучит просто, но на практике алгоритм полон тонкостей: covariant и contravariant позиции, приоритеты кандидатов, странное поведение пересечений и неочевидные ограничения. Разберём всё по порядку.</p><p>TypeScript выводит типовые переменные в две фазы: сбор кандидатов из аргументов и контекста, затем свёртка списка в единственный тип.</p><p>Кандидаты делятся на ковариантные (выходные позиции) и контравариантные (входные позиции); контравариантный результат обычно побеждает.</p><p>При свёртке TypeScript ищет общий супертип в списке кандидатов, но никогда не выбирает тип, которого нет в списке.</p><p>Пересечения типов ведут себя непредсказуемо: иногда TypeScript «снимает» литеральный тип, иногда захватывает весь объект.</p><p>В generic-контексте условные типы (extends ? :) не вычисляются, что ломает инференс через Exclude и подобные утилиты.</p><p>NoInfer&lt;T&gt; (с TypeScript 5.4+) позволяет блокировать вывод типа из конкретной позиции — полезно для API со значениями по умолчанию.</p><h2>Фаза 1: сбор кандидатов</h2><p>Когда TypeScript видит вызов функции с дженериком, он сопоставляет типы аргументов (source types) с типами параметров функции (target types). Каждый раз, когда «прогулка» по типам доходит до «голого» type parameter в target, соответствующий source type записывается как кандидат.</p><h3>Ковариантные и контравариантные кандидаты</h3><p>Кандидаты собираются в два списка. Если type parameter находится в позиции аргумента функции или возвращаемого значения — это <b>ковариантный</b> (output position) кандидат. Если type parameter находится в позиции параметра callback-функции — это <b>контравариантный</b> (input position) кандидат.</p><h3>Условные типы и infer</h3><p>Когда TypeScript встречает условный тип, кандидаты собираются только из той ветки, которая теоретически может сработать. Но важно: <b>само условие не добавляет кандидатов</b>. Запись T extends Foo НЕ добавляет Foo как кандидат для T.</p><p>Это классическая ловушка: кандидаты собираются из одной ветки, а типовая проверка — из другой. TypeScript <b>не вычисляет</b> условные типы на этапе инференса.</p><h3>Распределение по union</h3><p>Если source type — union, а target — не «голый» type parameter, TypeScript проходит по каждой ветке union отдельно и собирает кандидатов из всех веток в один список.</p><h3>Что НЕ учитывается при сборе</h3><ul><li>Условие условного типа (только ветки).</li><li>Constraints (&lt;T extends Foo&gt;) — они проверяются позже, на этапе разрешения.</li><li>Дефолтные значения type parameter (&lt;T = unknown&gt;) — используются только если список кандидатов пуст.</li><li>Супертипы source type.</li></ul><h2>Фаза 2: разрешение кандидатов</h2><p>После сбора TypeScript сворачивает каждый список в единственный тип. Алгоритм зависит от вариантности и содержимого списка.</p><h3>Ковариантные кандидаты</h3><ol><li>Если все кандидаты — литералы одного базового типа, они объединяются в union.</li><li>Иначе ищется кандидат, который является строгим супертипом всех остальных. Если найден — он выбирается.</li><li>Если нет — выполняется left-reduce: начинаем с первого кандидата, идём по списку, заменяя текущий на супертип, если встречаем его.</li></ol><p><b>Важно:</b><br />Если в списке кандидатов нет общего супертипа, TypeScript выбирает <b>первый</b> элемент. Это частая причина неожиданных ошибок при передаче разнородных аргументов одного дженерика.</p><h3>Контравариантные кандидаты</h3><p>Для контравариантных кандидатов логика зеркальная: ищется общий <b>подтип</b>, иначе — left-reduce с подтипами. Если оба списка (ковариантный и контравариантный) непусты, контравариантный результат обычно побеждает — за исключением случаев, когда ковариантный результат является подтипом контравариантного.</p><h3>Приоритеты кандидатов</h3><p>Каждый кандидат помечается приоритетом. Кандидат с более высоким приоритетом <b>стирает</b> все кандидаты с более низким приоритетом. Основные приоритеты: None (0), NakedTypeVariable (1), MappedTypeConstraint (32), ReturnType (128), LiteralKeyof (256).</p><p>Приоритеты MappedTypeConstraint, ReturnType и LiteralKeyof — «комбинационные»: при них результатом становится union или intersection всех кандидатов, а не единственный тип.</p><h2>Пересечения: загадочное поведение</h2><p>Одно из самых неинтуитивных мест — вывод через пересечения. Рассмотрим несколько примеров.</p><p>Почему так? Это эвристики компилятора, направленные на максимизацию полезности захваченного типа. В первом случае полезнее получить весь объект, во втором — сохранить литеральную точность строки. Порядок операндов пересечения не влияет на результат.</p><h2>Вывод в generic-контексте</h2><p>Когда инференс происходит внутри другой generic-функции, условные типы <b>не могут быть вычислены</b>, потому что type variables ещё не связаны. Это ломает паттерны вроде Exclude, если вызывать их через generic-обёртку.</p><p>В типовых enterprise-проектах, где активно используются типовые утилиты для валидации API (например, в проектах на NestJS или с Zod), это ограничение часто приводит к необходимости явно прописывать generic-аргументы или реструктурировать типы.</p><h2>NoInfer: как управлять выводом</h2><p>Начиная с TypeScript 5.4, в языке есть встроенный вспомогательный тип NoInfer&lt;T&gt;. Он блокирует сбор кандидатов для T из той позиции, где используется NoInfer&lt;T&gt;.</p><p>Это особенно полезно в библиотечном коде, где нужно разделить «источник истины» для вывода типа и позиции, которые должны ему подчиняться.</p><h2>FAQ</h2><h2>Выводы</h2><p>Теперь вы знаете, как TypeScript выводит типы переменных: через сбор кандидатов из аргументов, свёртку списка с учётом вариантности и приоритетов, а затем проверку результата на соответствие constraints. Алгоритм вывода типовых переменных — это тщательно продуманная, но не лишённая сюрпризов система. Две фазы (сбор и разрешение), вариантность, приоритеты и эвристики для пересечений создают поверхность, которую сложно угадать интуитивно, но которую можно понять, зная правила.</p><p>Ключевые практические выводы для разработчиков:</p><ol><li>Если функция принимает несколько аргументов одного дженерика — убедитесь, что они совместимы, иначе TypeScript выберет первый кандидат и отвергнет остальные.</li><li>Не полагайтесь на супертипы для инференса: они не попадают в список кандидатов.</li><li>В generic-контексте условные типы «замораживаются» — проектируйте API с этим ограничением.</li><li>Используйте NoInfer&lt;T&gt; в библиотечном коде для точного контроля над источниками вывода типов.</li><li>При странном поведении с пересечениями — перепишите типы явно или добавьте constraint.</li></ol><blockquote>Понимание алгоритма вывода типовых переменных позволяет писать более надёжные типовые абстракции и тратить меньше времени на отладку сложных generic-конструкций.</blockquote><p><b>Источники:</b></p><ul><li><a href="https://norswap.com/typescript-type-variable-inference">How TypeScript infers type variables — Nicolas Laurent (norswap)</a></li><li><a href="https://norswap.com/typescript-distribute">How TypeScript distributes unions — Nicolas Laurent (norswap)</a></li><li><a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">TypeScript Handbook: Generics</a></li></ul><p>Если у вас есть примеры неожиданного поведения TypeScript с дженериками — делитесь в комментариях. Соберём коллекцию «типовых загадок» вместе.</p>]]></content:encoded>
    </item>
    <item>
      <title>Почему главные конфликты в разработке не связаны с технологиями</title>
      <link>https://tproger.ru/articles/pochemu-glavnye-konflikty-v-razrabotke-ne-svyazany-s-tehnologiyami</link>
      <comments>https://tproger.ru/articles/pochemu-glavnye-konflikty-v-razrabotke-ne-svyazany-s-tehnologiyami?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Андрей Козлов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pochemu-glavnye-konflikty-v-razrabotke-ne-svyazany-s-tehnologiyami</guid>
      <description><![CDATA[<p>Почему конфликты в IT-командах возникают не из-за технологий, а из-за коммуникации, идентичности и инженерной культуры. Разбор споров вокруг Scala, Kotlin, архитектуры, технического долга и командной динамики в разработке.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pochemu-glavnye-konflikty-v-razrabotke-ne-svyazany-s-tehnologiyami">Почему главные конфликты в разработке не связаны с технологиями</a>»</p>]]></description>
      <category><![CDATA[Алгоритмы и структуры данных]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Scala]]></category>
      <category><![CDATA[HR]]></category>
      <category><![CDATA[Kotlin]]></category>
      <category><![CDATA[Lego]]></category>
      <category><![CDATA[Тимлид]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 15 May 2026 06:19:51 GMT</pubDate>
      <content:encoded><![CDATA[<p>В первый месяц работы юный разработчик может думать, что причина конфликтов в команде скрывается в технологических нюансах. Через год он начинает подозревать: дело в чем-то другом. Десять лет спустя он с уверенностью скажет: технологии здесь вообще ни при чем.</p><h2>Инструмент как часть личности</h2><p>Возьмем типичную ситуацию: переход на новый стек или поддержка старой базы. Допустим, команда третью неделю (а иногда и третий месяц!) обсуждает, оставить Scala или перейти на Kotlin. Аргументы обеих сторон озвучиваются на первой же встрече, а затем просто повторяются от созвона к созвону. Кто-то говорит об экосистеме, кто-то — о кривой обучения. Внешне это выглядит как техническая дискуссия, но решение не принимается.</p><p>За годы работы с инженерными командами я заметил закономерность: чем дольше тянется спор, тем меньше в нем остается от сути технологий. Люди спорят о синтаксисе, но неозвученная дискуссия идет о том, чья экспертиза весит больше, чья позиция будет услышана и кто в итоге будет нести ответственность.</p><p>Программист, который пять лет пишет на Scala, не просто использует инструмент. Язык стал частью его мышления: как он декомпозирует задачи и выстраивает архитектуру. И когда кто-то предлагает перейти на Kotlin, человек слышит: «То, что ты умеешь, больше не нужно». Отсюда и высокий градус эмоций. Годы практики делают стек частью профессиональной идентичности. Оспорить выбор инструмента — значит задеть человека лично.</p><h2>Ловушка единомышленников</h2><p>Отдельная история — когда вся команда пришла из одной среды. Если вы учились в одном вузе, слушали тех же спикеров и одновременно осваивали стек — на старте это кажется преимуществом. Вы понимаете друг друга с полуслова.</p><p>Однако со временем команда перестает замечать собственные слепые пятна. Любой «человек со стороны» с его новым взглядом воспринимается как дилетант, пытающийся найти изъяны в идеальной системе. Холивары в таких коллективах особенно вязкие: все одинаково уверены, что «очевидное очевидно», а любое альтернативное мнение априори ошибочно или упоминается лишь в контексте «идея хорошая, но делать ее мы не будем».</p><h2>Скульптура против Lego: конфликт ценностей</h2><p>В инженерной культуре сложность часто путают с качеством. Из-за этого возникают одни из самых затяжных конфликтов: когда один разработчик ратует за лаконичность и скорость, а другой — за «высокую архитектуру». Scala появилась именно из этой логики: язык создавался людьми, которые хотели объединить объектно-ориентированное и функциональное программирование в одном инструменте. Получилось мощно и академически красиво, но взаправду сложно. И вопрос, а всегда ли нужно вот настолько сложно, тоже порождает споры.</p><p>Хорошая аналогия — скульптура из мрамора против фигурки из Lego. Когда мы видим работы Страцца и Бернини, которые смогли передать в камне эффект тончайшей вуали, мы застываем в восхищении посреди музея. Но создание таких статуй требовало времени, а внести “правки от заказчика” означало бы подвергнуть риску всю работу: попробуйте исправить скол на мраморной статуе, которую ваяли сильно дольше, чем планировалось. В случае с конструкцией из Lego все намного проще: вынул блок, вставил другой, и вот из фигурки кошки уже получился «Тысячелетний сокол».</p><p>Чем сложнее язык или технология, тем глубже оказываются ошибки и тем труднее их потом найти. Причем платит за это не только тот, кто писал. Автор ошибки как раз легко может к этому моменту уже уйти на повышение. Но каждый следующий человек, который начнет взаимодействовать с этим кодом, заплатит ту же цену. Технический долг в командах почти всегда описывают одинаково: «это то, что написали до меня». Мы сильно реже склонны упоминать его, имея в виду собственный код и свои планы. Сложные решения, выбранные ради красоты концепции, — один из самых надежных способов создать именно такой долг.</p><p>Сложный инструмент — не приговор. Но когда его выбирают ради престижа, а не задачи, споры вокруг него становятся особенно изматывающими.</p><h2>Конфликт как трудности перевода</h2><p>Важная вещь, которую часто пропускают: разные люди воспринимают одно и то же взаимодействие по-разному. Для одних горячий спор — нормальный способ думать вслух и нащупывать решение. Для других тот же разговор выглядит как конфликт, который нужно срочно разрядить или который надо зарепортить, тимлиду или сразу в HR.</p><p>Представьте, что два инженера увлеченно спорят об архитектуре: перебивают друг друга, говорят громко, каждый настаивает на своем. Третий коллега идет к руководителю и взволнованно сообщает, что в команде глубокий конфликт. Двое из начала абзаца искренне удивляются: какой конфликт, мы просто разговаривали. Но наблюдатель снаружи видит драку.</p><p>Проблема возникает, когда команда не договорилась, что считать нормой. Один воспринимает прямую полемику как рабочий режим, другой как личную атаку. Они будут решать разные задачи в одном разговоре и никогда не придут к общему решению, пока не поймут, что разговаривают на разных языках.</p><h2>Молчание как последствие конфликтофобии</h2><p>Если порыться в памяти, мы вспомним истории, когда инженер предлагал идею, но получил отказ — причем без объяснений, просто нет. Инженер предлагает снова, и снова получает неаргументированный отказ. В такой ситуации мало кто предложит инициативу в третий раз. Психологи называют это выученной беспомощностью — человек перестает пробовать, потому что система не воспринимает предложения. А объяснять детали система не обучена или не планирует, ведь чем больше информации она даст участникам системы, тем больше поводов для споров может возникнуть.</p><p>Проходит полгода, и руководители удивляются: почему никто не проявляет инициативы, почему все приходится тянуть самому. Получается, что боязнь конфликтов через неспособность доносить аргументы порождает дальнейшие проблемы.</p><p>А люди оптимизируют поведение под реальные условия. Те, кто уходит из команд, где их не слышали, редко называют настоящую причину даже во время exit-интервью — говорят про оффер, зарплату, должность. Такой отток не попадает ни в какой дашборд, и поэтому его не замечают.</p><p>Маленькое трение, умноженное на количество людей и рабочих дней, становится реальной проблемой производительности. Разница в том, что не всегда можно четко измерить, когда человек не доволен. Это не видно на графиках, и поэтому проще игнорировать, но трение точно ощущается человеком изо дня в день.</p><p>Команды, которые застревают в спорах о технологиях, обычно делают это не из-за недостатка экспертизы. Оказывается, что, когда нет безопасного способа не соглашаться и обсуждать разные мнения, никакие условия труда и семинары по управлению конфликтами ситуацию не улучшат. Но чтобы исправить эту проблему, сначала нужно признать, что это вообще проблема — и, главное, проблема не техническая.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как связаны Atomic CSS и Функциональное программирование</title>
      <link>https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie</link>
      <comments>https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рамазан Максютов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie</guid>
      <description><![CDATA[<p>Почему подход Atomic CSS называют функциональным? Именно на этот вопрос я постараюсь ответить в данной статье! Сначала я опишу вам базовые принципы ФП, а затем расскажу про основы Atomic CSS, проводя аналогии с функциональным программированием. </p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-svyazany-atomic-css-i-funkcionalnoe-programmirovanie">Как связаны Atomic CSS и Функциональное программирование</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[CSS]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 27 Apr 2026 07:46:42 GMT</pubDate>
      <content:encoded><![CDATA[<p>Всем привет, друзья!</p><p>Меня зовут Рамазан, я Frontend-разработчик и энтузиаст, который любит искать свежий взгляд на привычные вещи в Web-разработке.</p><p>Думаю, что вы слышали когда-либо про функциональное программирование (далее - ФП). Это парадигма для которой характерно применение чистых функций и сохранение иммутабельности данных. Существует ряд языков, в которых принципы ФП преобладают: Haskell, OCaml и Elixir. Однако и другие языки вроде JavaScript, Python, C++ поддерживают этот подход, хотя только им не ограничиваются.</p><p>Но внимательный читатель посмотрит на название и спросит: "Функциональное программирование - это хорошо. А причём здесь Atomic CSS?". Сейчас отвечу! Всё дело в том, что ещё на заре появления атомарного подхода у него существовало и другое название - Functional CSS. Кто-то довольно часто использует его и сейчас, чтобы не путать с одноимёнными <a href="https://css-tricks.com/the-atomics/" rel="nofollow">терминами</a>. Но почему этот подход к CSS-стилизации называют функциональным?</p><p>Именно на этот вопрос я постараюсь ответить в данной статье! Сначала я опишу вам базовые принципы ФП, а затем расскажу про основы Atomic CSS (далее - ACSS), проводя аналогии с функциональным программированием. Кроме того, я постараюсь на простых примерах показать, какие проблемы решаются, если применять Atomic CSS при стилизации. Когда я готовил материалы для этой статьи, я много опирался на <a href="https://www.youtube.com/watch?v=7g0BHu0kWXo" rel="nofollow">этот </a>и на <a href="https://www.youtube.com/watch?v=uHVqbCPnOwU" rel="nofollow">этот </a>доклады. Наконец, хотел заметить, что это перевод моей оригинальной <a href="https://www.freecodecamp.org/news/atomic-and-functional-css" rel="nofollow">статьи</a> на английском языке — так что можете глянуть и её.</p><p>Ну что, держитесь крепче - мы начинаем!</p><h2>Что нужно знать</h2><p>Чтобы разобраться в этой статье, все, что вам нужно, - это базовое понимание HTML, CSS и JavaScript. Также в статье имеется несколько примеров, в которых мы будем использовать Atomic CSS фреймворк [mlut](https://mlut.style/), но вам не обязательно знать его синтаксис, потому что я привёл расшифроку его утилит в рукописном CSS.</p><h2>Базовые принципы ФП</h2><p>Функциональное программирование - это большая область, в которой написано немало сложных статей и которой посвящен целый <a href="https://www.cambridge.org/core/journals/journal-of-functional-programming" rel="nofollow">научный журнал</a>. Поэтому я в своей статье сосредоточусь на том, чтобы изучить только базовые принципы ФП и провести аналогию с ними в Atomic CSS.</p><p>Этот подход основывается на том, что все необходимые для нас действия в программе мы должны совершать посредством вызова некоторых функций и их композиций.</p><p>Давайте я приведу основные понятия, через которые попытаюсь раскрыть суть данного подхода:</p><p>1) Чистые функции;</p><p>2) Иммутабельность;</p><p>3) Композиция функций.</p><h3>Чистые функции</h3><p>Функция называется чистой, если она:</p><p>- при одних и тех же входных параметрах возвращает одно и то же значение;</p><p>- не имеет побочных эффектов (изменений внешних значений или сущностей).</p><p>Приведу для наглядности пару примеров.</p><p>Первая функция чистая, ведь, если мы будем вводить одни и те же аргументы, то будем получать один и тот же результат. Кроме того, никакие глобальные переменные эта функция не меняет, а объекты не мутирует.</p><p>Вторая функция не попала в понятие чистоты по обоим пунктам. Она меняет внешнюю переменную `s` и использует для вычислений другую внешнюю переменную `c`, которая может меняться.</p><p>Чистые функции позволяют писать более предсказуемый код. Приложение может разрастись, а функция с неявным параметром, который может меняться со временем, может привести к большим сложностям в дебаге и поддержке кода.</p><h3>Иммутабельность</h3><p>Иммутабельность - это принцип, в соответствии с которым объекты данных не должны меняться после создания. Чтобы произвести изменения над данными, необходимо создать новый их экземпляр и работать с ним.</p><p>Поначалу может показаться, что таким образом мы лишаем себя гибкости в процессе разработки и уменьшаем скорость работы программы. Но в действительности, если в языке или рантайме есть оптимизации для иммутабельных данных, соблюдение этого принципа помогает избегать многих ошибок и использовать параллельные вычисления.</p><p>Вот довольно простой пример: возьмём компонент React, который отображает задачу в списке дел. Состояние задачи описывается объектом. И чтобы React правильно перерисовал состояние компонента задачи, когда пользователь что-то в нём изменяет, необходимо передать в качестве нового состояния не измененный старый объект, а новый экземпляр объекта с текущим состоянием. Вот пример, где для состояния задачи используется иммутабельный объект:</p><p>Здесь мы увидим, что нажатие на кнопку приведёт к изменению состояния задачи и вызовет повторную прорисовку компонента. Однако мы могли бы определить функцию `toggleDone()` иным способом, используя мутации объекта:</p><p>При использовании такого обработчика событий эффект не будет достигнут, поскольку ссылка на объект остаётся прежней, несмотря на то что сам объект был изменён.</p><h3>Композиция функций</h3><p>Композиция функций подразумевает применение результата выполнения одной функции в качестве аргумента другой функции. Приведём пример программы, которая делает первую букву каждого слова заглавной:</p><p>Здесь мы определили функцию `compose(f1, f2)`, которая возвращает композицию функций, переданных ей в аргументах. Дальше мы используем эту функцию для создания функции `capitalize()`, которая будет как раз делать только первые буквы слов заглавными посредством композиции функций `lower()` и `upperEveryFirst()`. Сначала выполняется первая из них, и она возвращает строку со всеми строчными буквами в аргумент второй функции. Вторая же функция делает первую букву каждого слова заглавной.</p><p>В больших проектах и при необходимости сложных вычислений такие композиции могут быть и несколько больше, а логика в них может быть куда более богатой. И в этих случаях такой подход к проведению вычислений помогает разбить очень объёмные преобразования на серию относительно простых и компактных функций, которые применяются одна за другой. Это повышает удобство разработки, рефакторинга и отладки кода.</p><h2>Как принципы ФП находят отражение в Atomic CSS</h2><p>Теперь, когда мы узнали немного про функциональное программирование, попробуем ответить на вопрос: "А причём здесь Atomic CSS?". Проведём аналогию между этими подходами на уровне описанных выше принципов.</p><h3>О том, что такое Atomic CSS</h3><p>Но сначала уделим немного времени тому, что такое Atomic CSS. Это методология вёрстки, в которой мы используем маленькие атомарные CSS-правила, каждое из которых делает одно действие. Эти классы называют утилитами. Обычно они применяют одно CSS-свойство, но не обязательно одно. Например, во фреймворке [mlut](https://mlut.style/) утилите `Bgc-red` соответствует свойство `background-color: red`, а утилите `-Sz50p` - сразу два свойства `width: 50%` и `height: 50%`.</p><p>Современные Atomic CSS фреймворки, типа mlut и Tailwind, внутри используют так называемый JIT-движок. Это компонент, который генерирует CSS на основе только тех утилит, которые вы использовали в разметке.</p><h3>Чистота</h3><p>Чистота в CSS определяется тем, какими селекторами, а если конкретнее - классами, задаётся стилизация элементов. В чистом CSS поведение элемента должно определяться исключительно теми классами, которые привязаны к нему в атрибуте `class`. То есть в файле со стилями в идеале не должно быть селекторов вроде `section`, `div &gt; ul`.</p><p>Во-первых, они задают слишком общие правила, которые с большой вероятностью должны быть нарушены или дополнены в той или иной части проекта. Поэтому, когда мы будем стилизовать конкретные элементы, нам придётся постоянно держать в голове эти стили, чтобы понять, как аккуратно достичь необходимой стилизации, ничего не испортив.</p><p>Во-вторых, нарушается чистота CSS для каждого конкретного элемента. Допустим, мы имеем следующую разметку:</p><p>А CSS будет таким:</p><p>В итоге мы получим, что первая кнопка окрашивается в красный цвет, а вложенная - в зелёный. На таком примере это кажется довольно безобидным, но это будет приносить неудобства, когда структура проекта сильно разрастётся. И здесь мы видим, что результат работы собственных стилей класса `.greeting` зависит от того, где соответствующий элемент находится. Это аналогично тому, что функция при одних и тех же вводных данных даёт разные результаты в зависимости от того, где вызывается.</p><p>Atomic CSS позволяет такого эффекта избежать. В этом подходе в большинстве случаев стили применяются только к тем элементам, для которых прописаны соответствующие классы. Если же необходимо сделать несколько одинаковых элементов, то одна и та же утилита прописывается в атрибут `class` каждого такого элемента.</p><p>Аналогичный пример можно переписать на mlut вот так:</p><p>Где JIT-движок mlut сгенерирует такие стили:</p><p>Здесь мы уже видим, что стили элементов в таком подходе будут зависеть только от классов, которые им приписаны.</p><p><i>Замечание</i>: здесь, правда, стоит заметить, что синтаксис mlut позволяет делать вещи, которые отходят от строгого понятия чистоты CSS. Иногда это бывает необходимо для создания относительно более сложных эффектов. Допустим, мы хотим реализовать такую карточку, при наведении на которую меняется цвет фона у вложенной в неё кнопки. Тогда на mlut нужно будет написать следующее:</p><h3>Иммутабельность</h3><p>Под иммутабельностью CSS я буду подразумевать то, что стили элементов не переписываются. Иммутабельность в ACSS означает, что утилиты, как правило, не мутируют друг друга. Например, в BEM основные стили задаёт блок или элемент, а модификатор эти стили мутирует. А в других подходах, где используются комбинированные селекторы, это мутирование происходит чаще и менее явно.</p><p>Попробую привести простой пример. Пусть у нас есть карточка с товаром, которая может быть в своём дефолтном состоянии, либо в состоянии выделения. В BEM это бы выглядело примерно следующим образом:</p><p>В этом примере мы видим, что класс `product-card` задаёт красный цвет фона карточки по умолчанию. И, чтобы как-то обозначить выбранную карточку посредством другого цвета фона, нам приходится добавлять класс-модификатор, который изменяет цвет с красного на зелёный. И делает он это посредством переписывания свойства `background-color`, то есть с помощью мутирования стилей блока.</p><p>В подходе Atomic CSS эта проблема решается, так как утилиты позволяют задавать CSS-свойства независимо друг от друга и задавать модификации, не прибегая к мутации. Вот так будет выглядеть данный пример, если использовать mlut:</p><h3>Композиция</h3><p>В функциональном программировании повсеместно используются композиции функций. В атомарном CSS аналогом композиции функций служит композиция утилит при стилизации элементов. Как в ФП мы посредством последовательного применения множества простых функций получаем сложное поведение, так и в ACSS посредством множества простых утилит мы можем получить нетривиальную стилизацию.</p><p>Для примера покажу простой смайлик, который сделан посредством только лишь Atomic CSS:</p><p>Так будет выглядеть наш результат:</p><figure><img src="https://media.tproger.ru/user-uploads/134620/2026-03-31/3d47e497-4595-4167-af65-b9b4ece345c8.webp" alt="Простой CSS арт" /><figcaption>Простой CSS арт</figcaption></figure><p>Таким образом, применяя утилиты одну за другой, мы получили даже небольшой CSS-арт.</p><h2>Заключение</h2><p>Подводя итоги, скажу, что Atomic CSS действительно воплощает в себе базовые принципы функционального программирования. Конечно, не буквально, но в том смысле, который актуален для Frontend-разработчиков и верстальщиков. Я был бы рад услышать ваши дополнения и возражения в комментариях - будет интересно их почитать и над ними подумать!</p><p>Напоследок скажу: смотрите на привычные вещи свежим взглядом!</p><p>И, как обычно, успехов вам в увлекательном пути Frontend-разработки!</p>]]></content:encoded>
    </item>
    <item>
      <title>Гайд: Как использовать ChatGPT, чтобы стать программистом</title>
      <link>https://tproger.ru/articles/kak-ispolzovat-chatgpt-dlya-obucheniya-programmirovaniyu</link>
      <comments>https://tproger.ru/articles/kak-ispolzovat-chatgpt-dlya-obucheniya-programmirovaniyu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Владислав Устинов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-ispolzovat-chatgpt-dlya-obucheniya-programmirovaniyu</guid>
      <description><![CDATA[<p>ChatGPT для обучения программированию. Показываем, как стать программистом, используя ChatGPT. Рассматриваем пошаговую инструкцию и основные нюансы ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-ispolzovat-chatgpt-dlya-obucheniya-programmirovaniyu">Гайд: Как использовать ChatGPT, чтобы стать программистом</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Google]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Нейронные сети]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[OpenAI]]></category>
      <category><![CDATA[Английский]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Markdown]]></category>
      <category><![CDATA[Промпты]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 06 Jun 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Как формулировать вопросы для максимальной пользы</h2><h3>Конкретика вместо общих фраз</h3><p>Чтобы эффективно использовать ChatGPT, важно понимать, как он обрабатывает запросы. Принцип работы ИИ чат-ботов напоминает усложнённую версию Т9. Если упрощать, то нейросеть обучают на текстовых данных, где она находит связи между словами.</p><p>Когда мы отправляем наш текст, ГПТ его считывает, сопоставляет со своей базой и связями, которые он выучил. Далее, отталкиваясь от структуры и последовательности слов в нашем запросе, ИИ определяет оптимальную последовательность символов, слов и фраз, которые с ним связаны. Так формируется ответ.</p><p>У ChatGPT и аналогичных моделей обычно большие базы данных. Та же ChatGPT 3 имела 175 млрд параметров. ИИ учитывает эти параметры при составлении ответа. Слова в нашем запросе помогают нейросети правильно ориентироваться в данных: чем конкретнее запрос, тем более релевантную информацию ИИ сможет вытащить из базы. Благодаря чёткому и структурированному запросу мы как бы сужаем фокус поиска и помогаем сформулировать подходящий ответ.</p><h3>Как написать промт</h3><p>Наиболее распространённый способ написать рабочий промт — придерживаться следующей структуры:</p><ul><li>Контекст.</li><li>Роль.</li><li>Цель и задача.</li><li>Дополнительные детали.</li></ul><p>Благодаря такому подходу мы можем «сузить фокус» нейросети и получить более предсказуемый результат. Кратко рассмотрим элементы структуры промпта на примерах:</p><p><b>Контекст</b></p><p>В контексте мы описываем нашу ситуацию. Что мы делаем, для чего, с какими трудностями сталкиваемся и так далее.</p><p>Например:</p><blockquote>Я начинающий веб-разработчик, изучаю Python и фреймворк Flask. Для практики и закрепления полученных знаний я хочу создать своё первое небольшое, но полноценное веб-приложение — «Менеджер задач» (To-Do List). Я хочу продумать его структуру и основной функционал, прежде чем приступать к написанию кода.</blockquote><p><b>Роль с опытом</b></p><p>Далее прописываем нейросети роль. Мы прямо указываем, с позиции какого эксперта или персонажа нейросеть должна нам отвечать. Это помогает адаптировать стиль, глубину и направленность ответа. Например, объяснение для новичка тоном «ментора» будет отличаться от технического анализа от «старшего разработчика». Поэтому важно указать правильную роль под нашу задачу.</p><p>Пример:</p><blockquote>Представь, что ты опытный Full-stack разработчик и ментор. У тебя за плечами более 10 лет создания веб-приложений на Python (включая Flask и Django), и ты успешно наставлял многих начинающих разработчиков в их первых проектах. Ты знаешь, с какими типичными трудностями они сталкиваются и как лучше спланировать проект для эффективного обучения.</blockquote><p>Роль необязательно должна быть одна, мы их можем комбинировать. При этом важно прописывать каждой роли конкретные характеристики. Это могут быть примеры проектов, сфера деятельности, навыки и так далее.</p><p><b>Цель и задачи</b></p><p>Теперь, когда у нас есть контекст и роль, нужно чётко обозначить, чего мы ждём от чата ГПТ, какая у нас цель и как должен выглядеть итоговый результат.</p><p>Пример:</p><blockquote>Цель — получить подробный план и рекомендации для создания веб-приложения «Менеджер задач» на Flask. Задачи:<br /><br />Основные функции и MVP: Предложи ключевой набор функций для MVP (минимально жизнеспособного продукта) такого приложения (например, создание, просмотр, отметка о выполнении задач).<br /><br />Структура проекта и технологии: Опиши рекомендуемую структуру папок для Flask-приложения и посоветуй основные технологии (например, какую БД использовать для начала, что взять для простого фронтенда).<br /><br />Потенциальные сложности: Укажи на наиболее вероятные проблемы, с которыми я, как новичок, могу столкнуться при разработке.</blockquote><p>В сложных запросах полезно разбивать задачи на дополнительные подзадачи. Так, у нас будет ещё больше контроля, и мы получим более предсказуемый результат.</p><p><b>Дополнительные детали</b></p><p>Это последний штрих, который поможет получить ответ, максимально соответствующий нашим ожиданиям. Сюда можно добавить любую специфическую информацию: примеры, антипримеры, рекомендации, прочие нюансы.</p><p>Пример:</p><blockquote>Перед тем, как давать полный ответ, пожалуйста, кратко перечисли шаги (подумай шаг за шагом), которые ты предпримешь, чтобы ответить на мой запрос. Отвечай по существу, избегая излишне пространных введений, но при этом давая достаточно деталей по каждому пункту моих задач. Если предлагаешь какие-то конкретные библиотеки или инструменты (кроме Flask), давай краткое пояснение, почему они подходят для новичка в этом проекте.</blockquote><p>Иногда нейросети могут выдавать плохой ответ, даже на хорошо структурированный и конкретный запрос. Если ИИ капризничает, то это нормально, надо просто поиграться с уточнениями и формулировкой промпта.</p><h2>Лучшие практики обучения с ChatGPT</h2><h3>Искать баланс между конкретикой, структурой и краткостью</h3><p>Эффективное обучение с Chat GPT часто зависит от баланса между конкретикой и объёмом. Наш промпт получился довольно большим, но вполне конкретным. Важная оговорка: такие большие и сложные запросы не всегда уместны для мелких задач. Если мы учим английский для работы в IT и нам нужно перевести слово, или если мы хотим запомнить синтаксис цикла for в Python, то писать такую простыню текста будет избыточно. ИИ прекрасно справится с задачей без этих заморочек. Мы можем написать промпт по такому же подходу, но поменьше:</p><p>Пример:</p><blockquote>Я учу язык Python, сейчас прохожу циклы. Представь, что ты ментор, который специализируется на Python. Объясни, как устроен цикл for, когда его уместно использовать? Отвечай кратко.</blockquote><p>Либо, можно вообще отойти от такой структуры:</p><blockquote>Объясни, как устроен цикл for в Python.</blockquote><p>Нужно искать баланс между детализацией запроса и его сложностью. Чем труднее задача, тем конкретнее будет промпт и наоборот.</p><h3>Использовать разные чаты для разных задач</h3><p>Ещё важный нюанс: у чат-ботов обычно есть память. Они запоминают ход диалога, и это тоже влияет на качество ответов. Поэтому иногда чат может засориться. Например, мы генерируем программу обучения Python, потом просим дать задачки по алгоритмам на C#, потом закидываем кучу документации по react. При формировании ответов ИИ всё это помнит и сбивается. Поэтому под разные задачи стоит использовать отдельные чаты.</p><p>Кстати, по этой же причине не надо каждый раз прописывать контекст и роль в рамках одного диалога. Достаточно сделать это один раз, нейросеть всё запомнит.</p><p>Например, если мы хотим продолжить обсуждать цикл for, то нам уже не надо заново прописывать контекст и роль:</p><blockquote>Придумай 5 упражнений с циклом for.</blockquote><h3>Постепенно усложнять свои вопросы и пробовать альтернативные подходы</h3><p>Допустим, мы при обучении пишем функцию и она выдаёт ошибку. Вместо того чтобы писать один огромный промпт, мы можем разбить задачу на шаги и начать с малого. Сперва попросим объяснить конкретные части кода, потом перейдем к общей логике и разберем всю функцию, попросим подробнее разобрать ошибку и так далее.</p><p>После того как мы разобрались с нашей проблемой в коде, поняли, как он работает, можно попросить ИИ предложить альтернативные подходы. Например, спросить, как можно написать код по-другому.</p><h3>Использовать мета-промты</h3><p>Нам необязательно составлять промт самостоятельно, мы можем сделать универсальный шаблон для генерации запросов и отдать его ИИ. Промпт, который написал ИИ, принято называть мета-промтпом. Вот пример шаблона для генерации, его можно сохранить себе и адаптировать под свои задачи:</p><blockquote>Я начинающий {Указать специализацию}. Мне регулярно приходится изучать что-то новое, планировать структуру проектов, писать код, работать с документацией. В работе я использую нейросети, но на написание структурированных запросов уходит много времени.<br /><br />Представь, что ты опытный промпт-инженер, который специализируется на IT-проптинге и образовательных проектах. Ты регулярно пишешь конкретные, подробные и хорошо структурированные запросы для генерации кода, создания структуры, объяснения сложных тем по программированию.<br /><br />Твоя цель — помочь мне составить продуманный промпт для {описание задачи}. Я должен получить максимально подходящий и контролируемый результат. <br /><br />Для этого: составь подробный, структурированный и конкретный запрос для нейросети по следующей логике:<br />Контекст — здесь описана моя ситуация.<br />Роль — надо прописать роль или несколько ролей, которые обладают достаточными навыками и знаниями для решения моей задачи.<br />Цель и задача — здесь мы описываем желаемый результат, задачу, подзадачи.<br />Дополнительные детали — уточняем нюансы, требования, даём примеры, антипримеры и другую полезную информацию, которая поможет получить отличный результат.<br /><br />В качестве примера можешь опираться на структуру и детализацию этого запроса:<br />«Я начинающий веб-разработчик, изучаю Python и фреймворк Flask. Для практики и закрепления полученных знаний я хочу создать своё первое небольшое, но полноценное веб-приложение — «Менеджер задач» (To-Do List). Я хочу продумать его структуру и основной функционал, прежде чем приступать к написанию кода.<br />Представь, что ты опытный Full-stack разработчик и ментор. У тебя за плечами более 10 лет опыта создания веб-приложений на Python (включая Flask и Django), и ты успешно наставлял многих начинающих разработчиков в их первых проектах. Ты знаешь, с какими типичными трудностями они сталкиваются и как лучше спланировать проект для эффективного обучения.<br />Цель — получить подробный план и рекомендации для создания веб-приложения «Менеджер задач» на Flask. Основные задачи:<br /><br />Основные функции и MVP: Предложи ключевой набор функций для MVP (минимально жизнеспособного продукта) такого приложения (например, создание, просмотр, отметка о выполнении задач).<br /><br />Структура проекта и технологии: Опиши рекомендуемую структуру папок для Flask-приложения и посоветуй основные технологии (например, какую БД использовать для начала, что взять для простого фронтенда).<br /><br />Потенциальные сложности: Укажи на наиболее вероятные проблемы, с которыми я, как новичок, могу столкнуться при разработке.<br />Перед тем как давать полный ответ, пожалуйста, кратко перечисли шаги (подумай шаг за шагом), которые ты предпримешь, чтобы ответить на мой запрос. Отвечай по существу, избегая излишне пространных введений, но при этом давая достаточно деталей по каждому пункту моих задач. Если предлагаешь какие-то конкретные библиотеки или инструменты (кроме Flask), давай краткое пояснение, почему они подходят для новичка в этом проекте.»<br />Не придумывай факты обо мне. Будь пытливым, задавай дополнительные вопросы, чтобы лучше прописать промпт. Дай знать, если тебе понятна твоя задача.</blockquote><p>Другой способ упростить себе жизнь — использовать Gpts. Это пользовательские боты, которые сделаны под определённые задачи. Например, есть боты, которые специализируются на составлении запросов:</p><ul><li>Prompt Perfect.</li><li>Super Prompter.</li><li>Prompt Wizard.</li></ul><p>А вот ещё несколько полезных GPTs для разработчиков:</p><ul><li>Code Copilot — бот для программирования.</li><li>Code Tutor — ИИ, который учит программированию.</li></ul><ul><li>Python GPT — бот для программирования на Python.</li></ul><h2>Как использовать память и модели  ChatGPT для обучения</h2><h3>Что такое память и зачем она нужна</h3><p>Некоторые нейросети, как Gemini, Grok, ChatGPT, позволяют настроить глобальную память, что может быть полезно при обучении. Мы просто вписываем важную информацию в специальные формы в настройках. При работе во всех чатах ИИ помнит про эту информацию и пытается генерировать более персонализированные ответы. Вспомним, как мы пишем контекст в запросах.</p><p>Память — это что-то наподобие контекста, просто универсального. Мы можем добавить информацию о нашем стеке, должности, месте работы, нише и так далее. Вот пример настроенной памяти в ChatGPT:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/5019be01-2544-4124-932c-1270264cd35e.png" alt="ChatGPT для обучения программированию" /><figcaption>Настройка памяти ChatGPT: ввод данных и предпочтений</figcaption></figure><p>Также у GPT есть более глобальная память — возможность попросить что-то запомнить или забыть прямо в чате. Мы можем рассказать, чем занимаемся, что планируем, какие форматы ответов любим. ИИ будет учитывать эту информацию при формировании ответов в других диалогах.</p><p>Благодаря памяти мы можем настроить GPT под свои задачи при обучении программированию. Например, попросить отвечать кратко, либо объяснять информацию не сразу, а через намёки и подсказки, чтобы мы лучше усваивали материал.</p><h3>Какие в ChatGPT есть модели, для каких задач они подходят</h3><p><b>Модельный ряд Open AI</b></p><p>В чате ГПТ много моделей. Здесь есть GPT-4o, 4o mini, 4.5, o3, o4-mini, o4 mini-high, o3-mini, o1 / o1-mini.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/0460b5b7-213f-4ef0-96a9-da6aab6b7cf3.png" alt="Как стать программистом с ChatGPT" /><figcaption>Варианты моделей в Chat GPT</figcaption></figure><p>Для обучения программированию будет достаточно базовой GPT-4o. Её можно использовать вместо поисковика или ментора, чтобы разобраться в сложных темах.</p><p><b>Какие модели подойдут для разработки</b></p><p>Для разработки лучше подойдёт Gpt o3 или 4.1. Они хорошо справляются с задачами по математике и программированию. Правда, доступны только при платной подписке. Как альтернатива — в бесплатной версии можно использовать o4 mini-high. Но это не лучшая модель на рынке.</p><p>Если не хотите платить, то, возможно, стоит присмотреться к конкурентам Open AI. Тот же Google раздаёт свою топовую модель Gemini 2.5 Pro бесплатно в <a href="https://aistudio.google.com/app/library">AI Studio</a>. Согласно бенчмарку <a href="https://lmarena.ai/">lmarena</a>, Gemini 2.5 Pro обходит нейросети от Open AI в задачах по программированию. Также в кодинге себя хорошо показывает Grok3 и Cloude 3.7 Sonnet. Не стоит забывать и про китайские модели, прежде всего, DeepSeek.</p><h2>Основные сценарии использования ChatGPT с примерами промптов</h2><p>Итак, мы разобрались, как правильно составлять промпты, когда они уместны, как настроить ChatGPT и какие модели использовать для тех или иных задач. Рассмотрим несколько сценариев изучения программирования, при помощи ИИ чат-ботов.</p><h3>Объяснение сложных тем «на простом языке»</h3><p>Мы можем использовать нейросеть вместо поисковика, учебника или наставника. В программировании много непонятных для новичков тем: рекурсии, ООП, функциональное программирование. В базе ботов есть в том числе учебные материалы. ИИ может их достать и объяснить простым языком, адаптируя информацию под наш уровень.</p><p><b>Пример: объясни, что такое рекурсия</b></p><blockquote>Я начинающий Python-разработчик, сейчас разбираю тему рекурсий. Не могу понять, чем они отличаются от циклов. Представь, что ты опытный ментор, который уже 5 лет обучает новичков программированию на Python. Объясни, что такое рекурсия, чем она отличается от цикла и когда её лучше всего использовать. Ответ должен быть не сильно большим, используй примеры кода.</blockquote><p>Вот такой ответ от нейросети получим:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/4081b4f5-444b-4f99-9d00-952787b53770.png" alt="ChatGPT для обучения программированию" /><figcaption>Объяснение рекурсий от Chat GPT</figcaption></figure><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/6b6f0b5e-13e0-461b-ba10-ca8e26fcb47b.png" alt="Как стать программистом с ChatGPT" /><figcaption>Объяснение рекурсий от Chat GPT</figcaption></figure><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/e49075a3-7b33-419d-90e1-fca6bf2ce13f.png" alt="ChatGPT для обучения программированию" /><figcaption>Объяснение рекурсий от Chat GPT</figcaption></figure><h3>Помощь в написании и отладке кода</h3><p><b>Пример: помоги написать функцию сортировки</b></p><p>Допустим, мы учим питон и хотим попрактиковаться на простых задачах. Формулируем промпт так:</p><blockquote>Я учусь программировать на Python, недавно начал разбирать функции. Хочу попрактиковаться и написать функцию, которая сортирует список строк по длине (от самой короткой к самой длинной).<br /><br />Представь, что ты опытный Python-разработчик и ментор. У тебя за плечами более 10 лет опыта, ты обучаешь новичков и понимаешь, какие объяснения для них работают лучше всего.<br /><br />Моя цель — понять, как работает функция сортировки в Python. Покажи пример такой функции, объясни по шагам каждую строку.</blockquote><p>Результат:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/a1b22d5e-40e5-46a9-bb84-829887ef0561.png" alt="Как стать программистом с ChatGPT" /><figcaption>Функция сортировки на Python: ответ ChatGPT</figcaption></figure><p><b>Пример: проверь мой код на ошибки</b></p><p>Другой сценарий, это когда наш код не работает или работает не так.</p><blockquote>Я новичок в Python. Написал функцию, но она не работает — вылетает ошибка. <br />Вот код:<br />def hello(name):print("Привет," name)<br />hello("Мир")<br />Представь, что ты опытный преподаватель Python. Объясни, в чём ошибка, как её исправить и почему так нельзя писать. Дай исправленный пример.</blockquote><p>Результат:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/2800c8e4-fde8-4294-86f3-be5bda9139ba.png" alt="ChatGPT для обучения программированию" /><figcaption>Исправление ошибки в Python-коде от ChatGPT</figcaption></figure><h3>Генерация идей для проектов и упражнений</h3><p>Помимо разовых задач, ИИ может составить нам план для обучения. Это могут быть проекты, отдельные упражнения или полноценная программа.</p><p><b>Пример: составь подробный план обучения</b></p><blockquote>Я изучаю фронтенд-разработку. Уже знаю основы HTML, CSS и JavaScript. Хочу освоить React, чтобы уметь создавать интерфейсы и взаимодействовать с API.<br />Представь, что ты опытный фронтенд-разработчик и преподаватель. Составь подробный план изучения React на 3 месяца. Укажи, какие темы проходить каждую неделю, какие мини-проекты можно делать, и на что обратить особое внимание. План должен быть реалистичным для человека, который учится в свободное время (около 10 часов в неделю).</blockquote><p>Результат<b>:</b></p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/18b06e56-7922-44e4-88d0-d05a3b62ff3c.png" alt="Как стать программистом с ChatGPT" /><figcaption>план изучения React от ChatGPT: первая неделя</figcaption></figure><p><b>Пример: придумай проект под мои знания</b></p><blockquote>Я изучаю Python, прошёл основы: переменные, циклы, функции, списки. Хочу попрактиковаться на небольшом проекте, чтобы закрепить знания.<br />Представь, что ты ментор по Python с опытом преподавания. Придумай 3–5 простых проектов, которые я могу реализовать за выходные. Объясни, какую задачу решает каждый проект и какие навыки он помогает прокачать.</blockquote><p>Результат:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/5f037ff8-a7f3-4aff-a005-776095da07db.png" alt="ChatGPT для обучения программированию" /><figcaption>Идеи проектов на Python от ChatGPT</figcaption></figure><p><b>Пример: подготовь мне задания для практики</b></p><blockquote>Я изучаю JavaScript, пока прошёл только основы: переменные, функции, массивы. Хочу попрактиковаться.<br />Сыграй роль преподавателя, который даёт студенту задания для отработки материала. Придумай 5 упражнений, упорядочи их от простого к сложному. Желательно, чтобы они были с короткой формулировкой и подходили для ручной отработки без фреймворков.</blockquote><p>Результат:</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/ada9e56e-2a7c-4e36-8785-f62e0bb83569.png" alt="Как стать программистом с ChatGPT" /><figcaption>Упражнения на JavaScript от ChatGPT</figcaption></figure><h2>Ограничения и как их обходить</h2><p>Хотя потенциал ChatGPT при изучении программирования огромен, важно помнить о существующих ограничениях и способах их обхода для более продуктивной работы.</p><h3>Почему важно перепроверять ответы</h3><p>ИИ чат-боты не понимают код, а просто угадывают правдоподобную последовательность символов. Обычно угадывают правильно, но иногда могут придумать несуществующие функции, запутаться в синтаксисе или предложить устаревший подход.</p><p>Поэтому важно перепроверять ответы от нейросетей. Мы должны запускать каждый кусочек кода, уточнять в документации, спрашивать, как ИИ пришла к такому результату. Как вариант, можно просить подкреплять ответы ссылками на первоисточники.</p><h3>Как использовать дополнительные источники вместе с ChatGPT</h3><p>Если нам не хватает стандартной базы знаний нейросети, то мы можем заставить их работать с нашими документами, что-то искать в интернете или в репозиториях Github.</p><p><b>Выход в интернет</b></p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/bcced5cb-2930-41eb-a767-563e23d1a1ef.png" alt="ChatGPT для обучения программированию" /><figcaption>Интерфейс Chat GPT с включенной функцией поиска в интернете</figcaption></figure><p>Некоторые модели — вроде ChatGPT, Claude, Gemini, Perplexity и Grok — поддерживают подключение к сети. Это значит, что мы можем просить их найти актуальные решения, примеры кода, ошибки, официальную документацию или свежие статьи. Они работают как поисковик, только в виде диалога.</p><p><b>Работа с файлами и контекстное окно</b></p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/ff6585d9-2541-49d6-ab23-d6e16b2daf9c.png" alt="Как стать программистом с ChatGPT" /><figcaption>Пример запроса в Chat GPT c прикреплённым документом</figcaption></figure><p>Другой важный момент — работа с файлами. Мы можем загрузить код, ТЗ, документацию, таблицы или Markdown-файл с задачами. Модель проанализирует содержимое и будет использовать его при ответах. Это удобно, когда нужно задать вопрос по проекту и не хочется копировать всё вручную.</p><p>Однако важно учитывать размер документа и контекстного окна. Контекстное окно — это максимальный объём информации, которую нейросеть может запомнить в одном диалоге. У GPT-4o и Deep Seek оно около 128 тыс токенов, у Gemini, Grok и Qwen — 1 млн, у Claude — 200 тыс.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/0ebdd3a6-142b-478c-a344-b16c736b1ac7.png" alt="ChatGPT для обучения программированию" /><figcaption>Пример работы Gemini с полуторачасовым видео</figcaption></figure><p>Чем больше окно, тем больше данных можно скормить ИИ чат-боту — например, сразу весь репозиторий, длинную спецификацию или книгу. Модели от Google могут переваривать целые часовые видео.</p><p><b>Интеграция с GitHub</b></p><p>Чат ГПТ поддерживает интеграцию с GitHub. У нас есть три способа, как работать с репозиториями.</p><p>Вариант 1 — через GPT-ботов. Мы находим специальных ботов, которые созданы для работы с Github, и используем их.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/cc1a4031-d41a-47df-8a53-445f69764075.png" alt="Как стать программистом с ChatGPT" /><figcaption>Пример GPTs для работы с GitHub</figcaption></figure><p>Вариант 2 — можем работать с GitHub при помощи глубокого поиска из любого чата. Но у этого способа есть недостаток. В бесплатной версии мы можем использовать его только 5 раз в месяц.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/16ae4152-502a-4f1f-9ba9-bd0ce5749fb2.png" alt="ChatGPT для обучения программированию" /><figcaption>Пример запроса для поиска информации в GitHub при помощи Deep research</figcaption></figure><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/3e1907bb-b4d1-4c25-9d51-254b782da15e.png" alt="Как стать программистом с ChatGPT" /><figcaption>Результат работы Deep research</figcaption></figure><p>Ещё мы можем попробовать работать с GitHub при помощи обычного режима модели GPT 4o с включённым поиском в интернете, но не факт, что ИИ возьмёт данные именно с гитхаба.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/91dca560-b2eb-4f75-a667-196c72459bfe.png" alt="ChatGPT для обучения программированию" /><figcaption>Пример поиска информации при помощи Gpt 4o</figcaption></figure><p>В ChatGPT есть возможность привязать GitHub к нашему аккаунту Open AI и работать с репозиториями, без заморочек, в чатах. Делается это через настройки учётной записи.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/47a767c9-b8a8-4501-810c-037cd37e0804.png" alt="Как стать программистом с ChatGPT" /><figcaption>Настройки Chat GPT для привязывания GitHub</figcaption></figure><h2>Отличия платной и бесплатной версии ChatGPT для обучения программированию</h2><h3>Лимиты на использование моделей в платной и бесплатной версии</h3><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/e1efd036-1ea2-415d-a23e-622207fcb26c.png" alt="ChatGPT для обучения программированию" /><figcaption>тарифные планы Chat GPT</figcaption></figure><p>Самое заметное отличие — лимиты на использование моделей. Бесплатно мы получаем доступ к флагманской GPT-4o, но с ограничениями: обычно это около 10 сообщений на 3–5 часов (точное число зависит от загрузки серверов). Если лимит исчерпан, нас автоматически переключат на менее продвинутую GPT-4.1 mini. Это значит, что разбор сложного алгоритма или отладка бага может прерваться. Придётся ждать или довольствоваться ответами модели попроще.</p><p>В платной версии (например, Plus) лимиты «вкуснее»: до 80 сообщений к GPT-4o каждые 3 часа. Этого хватает для долгих диалогов: можно глубже погружаться в темы и не экономить запросы.</p><p>Ещё один важный момент — длина контекста: объём информации, который модель «помнит» в рамках одного диалога. GPT-4o и GPT-4.1 mini в бесплатной версии работают с окном в 128 тыс токенов. Это немало, но для анализа объёмного кода, нескольких файлов проекта или длинной документации может не хватить. В платной версии у нас есть доступ к GPT-4.1 с контекстным окном до 1 млн токенов. Здесь мы уже можем скормить проекты на сотни строк кода, и ИИ это всё переварит.</p><p>Ещё одна модель, которая заслуживает внимание — GPT o3. Это «думающая модель». Когда мы пишем запрос, то ИИ начинает генерировать разные формулировки по теме, как бы рассуждая. Далее, отталкиваясь от цепочек «рассуждений», получается сформулировать более точный ответ. У неё контекстное окно поскромнее — всего 200 тыс токенов, но зато она отлично справляется с задачами по логике, математике, хорошо подходит при программировании. Доступна только в платной версии.</p><h3>Задачи и напоминания в чатах</h3><p>Приятный бонус платной подписки — напоминания и планирование задач. В бесплатной версии этого нет. А с подпиской мы можем попросить ChatGPT, например, ежедневно присылать небольшую задачку по Python или напоминать о повторении материала по JavaScript.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/0a029e9b-ef52-4243-a931-345c09ecd29b.png" alt="Как стать программистом с ChatGPT" /><figcaption>Как работают напоминания в Chat GPT</figcaption></figure><p>Это похоже на личного AI-ассистента, который помогает учиться регулярно. Для этого ChatGPT использует модели o3 или o4-mini. Одновременно может быть до 10 активных задач.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/c6a8c143-aec1-4eab-8a51-23866d5574d0.png" alt="ChatGPT для обучения программированию" /><figcaption>Напоминание от Chat GPT</figcaption></figure><p>Когда придёт время напоминания, ChatGPT отправит уведомление на смартфоне, письмо на почту и пришлёт новое сообщение в чате.</p><h3>Голосовой мод с возможностью использовать камеру и транслировать экран</h3><p>Общаться с ChatGPT голосом можно и бесплатно (Standard Voice). Но платная подписка даёт Advanced Voice: он звучит естественнее и даже передаёт эмоциональные оттенки. Это предаёт интерактивности при обучении (ногда устаёшь печатать много текста).</p><p>Ещё в платной версии во время голосового чата на мобильных устройствах можно включать камеру и делиться экраном.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-05-21/331333fd-549e-4a82-a3c8-2a53dfae9b0e.png" alt="Как стать программистом с ChatGPT" /><figcaption>Слева интерфейс голосового мода Chat GPT, после того как через камеру показали код. Справа скриншот из IDE с кодом (это два разных интерфейса, просто для визуального удобства объединили в одно изображение).</figcaption></figure><p>Например, мы можем «проговорить» с ChatGPT сложную концепцию, получить обратную связь, или, столкнувшись с багом в мобильном приложении, показать его ИИ через камеру либо трансляцию экрана, чтобы вместе разобраться. Это добавляет интерактивности и полезно тем, кому проще воспринимать информацию на слух.</p><p>А как вы используете ChatGPT или другие нейросети для изучения программирования? У вас есть свои фишки, удачные примеры или не очень? Делитесь в комментариях и подписывайтесь на наш <a href="https://t.me/+iKEwDxvulHFkZDhi">тг-канал</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Большой гайд по инструментам для разработчиков от Tproger: фреймворки, базы, AI и DevOps в одной подборке</title>
      <link>https://tproger.ru/articles/bolwoj-gajd-po-instrumentam-dlya-razrabotchikov-ot-tproger--frejmvorki--bazy--ai-i-devops-v-odnoj-podborke</link>
      <comments>https://tproger.ru/articles/bolwoj-gajd-po-instrumentam-dlya-razrabotchikov-ot-tproger--frejmvorki--bazy--ai-i-devops-v-odnoj-podborke?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/bolwoj-gajd-po-instrumentam-dlya-razrabotchikov-ot-tproger--frejmvorki--bazy--ai-i-devops-v-odnoj-podborke</guid>
      <description><![CDATA[<p>Подборка топовых инструментов и технологий для разработчиков: от Elixir и DevOps-платформ до no-code, AI-инструментов и новых фреймворков.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/bolwoj-gajd-po-instrumentam-dlya-razrabotchikov-ot-tproger--frejmvorki--bazy--ai-i-devops-v-odnoj-podborke">Большой гайд по инструментам для разработчиков от Tproger: фреймворки, базы, AI и DevOps в одной подборке</a>»</p>]]></description>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Сервисы]]></category>
      <category><![CDATA[React]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[микро]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[CI/CD]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 04 May 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы собрали для вас огромную подборку наших статей про самые полезные инструменты, технологии и практики.
Если вы хотите работать быстрее, чище и с кайфом — сохраняйте себе этот гайд, чтобы не искать потом по всему интернету. Внутри — топовые фреймворки, AI-помощники, базы данных, лайфхаки и советы от практиков.</p><h2>Основные инструменты и технологии</h2><p>Статьи, с которых стоит начать, если хочется обновить стек или разобраться в новых подходах:</p><p><a href="https://tproger.ru/articles/top-60-luchwih-instrumentov-dlya-razrabotki-po-v-2025">ТОП 60 лучших инструментов для разработки ПО в 2025</a> — Собрали шестьдесят лучших инструментов для разработки программного обеспечения в 2025 году. От трекеров и редакторов до библиотек и фреймворков.</p><p><a href="https://tproger.ru/articles/top-11-trendov--kotorye-nuzhny-ajtiwniku-v-2025-godu">Топ 11 трендов, которые нужны айтишнику в 2025 году</a> — Представляем одиннадцать ключевых трендов в IT, которые будут актуальны в 2025 году. Краткий гид по технологиям, которые будут на слуху.</p><p><a href="https://tproger.ru/articles/obzor-populyarnyh-frejmvorkov-dlya-veb-razrabotki">Фреймворки, меняющие игру: выбираем идеальный инструмент для ваших веб-проектов</a> — Обзор современных веб-фреймворков, которые могут изменить подход к разработке ваших проектов.</p><p><a href="https://tproger.ru/articles/instrumenty-i-frejmvorki-qa--kotorye--ne--nuzhno-znat">Инструменты и фреймворки QA, которые (не) нужно знать</a> — о том, что реально используется в тестировании.</p><p><a href="https://tproger.ru/articles/chto-izuchat-nachinashhemu-razrabotchiku-na-c-">Что изучать начинающему разработчику на C#</a> — рассматриваем  языки, среды и подходы, которые пригодятся новичкам. Рекомендуем, с чего начать изучение C# и какие темы освоить в первую очередь.</p><p><a href="https://tproger.ru/articles/reactjs-na-izi--chto-realno-nuzhno-znat-frontend-razrabotchiku-v-2025-godu">ReactJS на изи: что реально нужно знать фронтенд-разработчику в 2025 году</a> — Краткий гайд по ключевым знаниям и навыкам, необходимым для работы с ReactJS в 2025 году.</p><h2>Базы, API, DevOps и CI/CD</h2><p>Набор инструментов и практик, которые помогут масштабироваться и не выгорать:</p><p><a href="https://tproger.ru/articles/top-10-instrumentov-devops--kotorye-uprostyat-vawu-zhizn-i-izbavyat-ot-nochnyh-relizov">Топ-10 инструментов DevOps, которые упростят вашу жизнь и избавят от ночных релизов</a> — must-have решения для DevOps-команд.</p><p><a href="https://tproger.ru/articles/postgresql-vs--clickhouse-vs--duckdb--kakuyu-opensors-bazu-vybrat-dlya-analitiki-v-2025-godu-">PostgreSQL vs. ClickHouse vs. DuckDB: какую опенсорс базу выбрать для аналитики в 2025 году?</a> — Сравниваем три популярные опенсорс СУБД для аналитики: возможности, производительность и кейсы использования.</p><p><a href="https://tproger.ru/articles/10-api--kotorye-sokratyat-vam-nedeli-razrabotki">Семь API, которые сократят вам недели разработки</a> — Подборка решений, которые можно быстро внедрить.</p><p><a href="https://tproger.ru/articles/razvorachivaem-instrumenty-ci-cd--praktiki-ot-devops-inzhenerov">Разворачиваем инструменты CI/CD: практики от DevOps-инженеров</a> — Делимся практическими советами по развертыванию инструментов CI/CD от профессионалов.</p><p><a href="https://tproger.ru/articles/kak-avtomatizirovat-prostye-zadachi-s-pomoshhyu-skriptov-">Как автоматизировать простые задачи с помощью скриптов?</a> — Гайд по быстрой автоматизации без боли.</p><p><a href="https://tproger.ru/articles/luchwie-praktiki-dlya-raboty-s-komandnoj-strokoj">Лучшие практики для работы с командной строкой</a> — Рассказываем, что такое командная строка. Рассматриваем пошаговую инструкцию по использованию.</p><h2>AI-инструменты и нейросети</h2><p>Что может помочь вам уже сейчас — от подсказок до генерации кода:</p><p><a href="https://tproger.ru/articles/top-5-ii-instrumentov-dlya-programmistov-v-2025">Топ-5 ИИ-инструментов для программистов в 2025 году</a> — Самые полезные AI-ассистенты по мнению редакции.</p><p><a href="https://tproger.ru/articles/deepseek-ili-claude--kakaya-nejroset-napiwet-kod--za-kotoryj-ne-stydno-">DeepSeek или Claude: какая нейросеть напишет код, за который не стыдно?</a> — Сравниваем возможности нейросетей DeepSeek и Claude в контексте генерации качественного кода.</p><p><a href="https://tproger.ru/articles/edge-ai--kak-rabotayut-nejroseti-na-ustrojstvah-s-ogranichennymi-resursami">Edge AI: как работают нейросети на устройствах с ограниченными ресурсами</a> — Объясняем, как нейросети работают на устройствах с ограниченными ресурсами и где это применимо.</p><p><a href="https://tproger.ru/articles/10-sposobov-zarabotat-na-iskusstvennom-intellekte-v-2025">10 способов заработать на искусственном интеллекте в 2025</a> — Рассказываем о десяти способах монетизации искусственного интеллекта в 2025 году.</p><h2>Утилиты, лайфхаки и неожиданно полезные штуки</h2><p>То, что экономит время, силы и нервы:</p><p><a href="https://tproger.ru/articles/sobral-11-sajtov--ekonomyashhih-vremya--kotorye-nuzhny-kazhdomu-razrabotchiku">11 сайтов, экономящих время, которые нужны каждому разработчику</a> — Подборка must-have ресурсов.</p><p><a href="https://tproger.ru/articles/luchwie-biblioteki-dlya-animacij-na-react">7 библиотек для анимаций на React</a> — Обзор популярных библиотек для создания анимаций в React: от простых эффектов до сложных переходов.</p><p><a href="https://tproger.ru/articles/30-samyh-poleznyh-bibliotek-python-dlya-veb-razrabotki-v-2024-godu">30 самых полезных библиотек Python для веб-разработки в 2024 году</a>  — Подборка тридцати полезных библиотек Python, которые пригодятся веб-разработчикам в 2025 году.</p><p><a href="https://tproger.ru/articles/7-programm-dlya-wifrovaniya-dannyh">7 программ для шифрования данных</a> —  Базовая кибер-гигиена для всех, кто работает с пользовательскими данными.</p><p><a href="https://tproger.ru/articles/top-samyh-poleznyh-magicheskih-komand-dlya-zavsegdataev-colab">Топ самых полезных магических команд для завсегдатаев Colab</a> — Рассказываем, как использовать магические команды в Colab, чтобы ускорить работу с данными и кодом.</p><p><a href="https://tproger.ru/articles/otkryvaem-cikl-statej-etl-dlya-zooparka-botov">5 ETL для обработки данных из Python-ботов</a> — Представляем пять ETL-инструментов, которые помогут автоматизировать сбор, трансформацию и загрузку данных от Python-ботов</p><p><a href="https://tproger.ru/articles/rabota-s-excel-gde-on-primenyaetsya-chem-polezen-i-gde-osvoit-etot-navyk-erid-ljn8klxkn">Работа с Excel: где он применяется, чем полезен и где освоить этот навык</a> — Объясняем, где и как используется Excel, почему он важен для аналитиков и где научиться работать с ним.</p><h2>Немного философии</h2><p>Когда хочется не просто выбрать инструмент, а понять, зачем он вам нужен:</p><p><a href="https://tproger.ru/articles/yazyk-elixir-i-funkcionalnoe-programmirovanie--chto-eto-za-zver-i-pochemu-on-horow-dlya-otkazoustojchivyh-sistem">Язык Elixir и функциональное программирование: что это за зверь и почему он хорош для отказоустойчивых систем</a> — Знакомим пользователей с Elixir и его применением.</p><p><a href="https://tproger.ru/articles/pochemu-mikroservisy-ne-nuzhny--antihajpovyj-razbor">Почему микросервисы не нужны: антихайповый разбор</a> — Анализируем случаи, когда микросервисная архитектура может быть излишней и неэффективной.</p><p><a href="https://tproger.ru/articles/10-luchwih-platform-dlya-sozdaniya-prilozhenij-bez-edinoj-strochki-koda">10 лучших платформ для создания приложений без единой строчки кода</a> — Обзор десяти лучших no-code платформ, позволяющих создавать приложения без программирования.</p><p>Скорее пользуйтесь нашим гайдом и читайте предыдущие. Вот, например, по <a href="https://tproger.ru/articles/bolwoj-gajd-po-mobilnoj-razrabotke-ot-tproger--poleznye-stati--praktiki-i-sovety">мобильной разработке</a> и <a href="https://tproger.ru/articles/bolwoj-gajd-po-react-ot-tproger--topovye-stati-i-instrumenty">React</a>.</p><p>Кстати! Забрать все самые топовые нейронки для айтишников можно в нашем <a href="https://tprg.ru/LN8a">большом гайде с 70+ ИИ-инструментами </a></p>]]></content:encoded>
    </item>
    <item>
      <title>Язык Elixir и функциональное программирование: что это за зверь и почему он хорош для отказоустойчивых систем</title>
      <link>https://tproger.ru/articles/yazyk-elixir-i-funkcionalnoe-programmirovanie--chto-eto-za-zver-i-pochemu-on-horow-dlya-otkazoustojchivyh-sistem</link>
      <comments>https://tproger.ru/articles/yazyk-elixir-i-funkcionalnoe-programmirovanie--chto-eto-za-zver-i-pochemu-on-horow-dlya-otkazoustojchivyh-sistem?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Владислав Устинов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/yazyk-elixir-i-funkcionalnoe-programmirovanie--chto-eto-za-zver-i-pochemu-on-horow-dlya-otkazoustojchivyh-sistem</guid>
      <description><![CDATA[<p>Что такое язык Elixir. Показываем сравнение языка Эликсир с другими. Рассматриваем преимущества и основные нюансы ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/yazyk-elixir-i-funkcionalnoe-programmirovanie--chto-eto-za-zver-i-pochemu-on-horow-dlya-otkazoustojchivyh-sistem">Язык Elixir и функциональное программирование: что это за зверь и почему он хорош для отказоустойчивых систем</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Ruby]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 22 Apr 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Основные принципы функционального программирования</h2><p>Elixir — функциональный язык программирования. Это значит, что здесь нет классов, объектов и прочих абстракций из ООП. Рассмотрим его особенности более детально.</p><h3>Чистые функции и отсутствие побочных эффектов</h3><p>Чистые функции делают код более предсказуемым и надёжным. У них есть два главных признака:</p><ul><li><b>Предсказуемый результат: </b>для одних и тех же входных аргументов такая функция всегда возвращает одно и то же значение. Её результат зависит исключительно оттого, что ей передали, в коде нет заимствований извне.</li><li><b>Отсутствие побочных эффектов: </b>она не взаимодействует с «внешним миром» — не изменяет данные вне своей области видимости, не выводит информацию на экран, не пишет в файлы, не зависит от случайных чисел или текущего времени.</li></ul><p>Рассмотрим на примере:</p><p>Функция sum — чистая, потому что она всегда возвращает один и тот же результат для одних и тех же входных данных (a и b). Если мы вызовем alculator.sum(10, 5), то всегда получим 15. Она не меняет никаких данных вне себя и не взаимодействует с внешним миром. Результат зависит только от её аргументов.</p><p>А это пример нечистой функции:</p><p>Функция sum_with_config выглядит похожей на sum, но она нечистая, потому что зависит от внешнего состояния и ведёт себя непредсказуемо.</p><p>Значение b берётся из файла config.txt. Если файл пуст, содержит 5 или, например, 100, результат будет разным, даже если аргумент a не изменился.</p><p>Код с чистыми функциями проще понимать, отлаживать, тестировать, рефакторить и переиспользовать. А ещё он хорошо подходит для задач, где важна безопасность при параллельном выполнении.</p><h3>Иммутабельность данных</h3><p>Elixir, как язык функционального программирования, поддерживает иммутабельность данных.</p><p>Вспомним, как мы обычно изменяем значения переменных в других языках на примере Python:</p><p>Мы создаём переменную x, она записывается в память, где ей присваиваем значение 10.</p><p>Далее мы присваиваем ей новое значение 11, и оно обновляется в памяти. Десятки больше нет.</p><p>Теперь рассмотрим похожий пример на Эликсире:</p><p>В память записывается значение 10, а не x. Только после этого присваиваем нашему значению «ярлык» в виде x.</p><p>Если у нас несколько переменных с одинаковыми значениями, то все они ссылаются на один участок памяти. В данном случае y ссылается на туже самую десятку.</p><p>Когда мы меняем значение переменной, мы на самом деле создаём новое в памяти, в данном случае 11, и присваиваем к нему новый «ярлык» — x.</p><p>Старое значение 10 остаётся, если у него есть другие ссылки или ярлыки. Если их нет, то его удаляет сборщик мусора. В данном случае 10 останется вместе с переменной y.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-04-15/e68eda57-9464-4c02-bd16-eeb12a6efb82.jpg" alt="" /><figcaption>Мутабельный и иммутабельный подход к переназначению переменных</figcaption></figure><p>Такой подход называют иммутабельностью. Он позволяет избежать ошибок, особенно когда несколько процессов читают одни и те же данные.</p><h3>Каррирование и композиция функций</h3><p><b>Каррирование </b>часто встречается в функциональных языках программирования. Оно означает, что мы превращаем функцию в цепочку, которая принимает только один аргумент.</p><ul><li>У нас есть функция sum(a, b), которая ожидает оба числа сразу.</li><li>После каррирования мы получаем функцию curried_sum(a).</li><li>Когда мы вызываем curried_sum(5), она не считает сумму. Вместо этого возвращает новую функцию. Назовём её условно add_five_func. Эта функция «запомнила», что a = 5, и теперь ждёт единственный аргумент — b.</li><li>Следующим шагом мы берём эту только что полученную функцию (add_five_func) и вызываем её, передавая ей значение 3 в качестве аргумента b.</li><li>Именно этот вызов (add_five_func(3)) запускает финальное вычисление (5 + 3) и возвращает нам результат 8.</li></ul><p>Благодаря каррированию код получается более модульным, функции можно любым образом комбинировать и проще тестировать.</p><p>Недостаток каррирования — код становится сложным, трудно читаемым. Ещё новые функции влияют на производительность, и во многих сценариях каррирование может быть избыточным.</p><p>В некоторых языках программирования, например, в Haskell или F#, есть фишки для более удобного синтаксиса каррирования и даже автоматическое каррирование, но не в Elixir. В нём всё приходится делать с награмождением и без плюшек под капотом.</p><p><b>Композиция </b>— это когда мы объединяем несколько функций в одну, где выходные данные одной функции передаются как входные данные для другой.</p><p>Представим, что нам нужно взять строку, убрать у неё лишние пробелы по краям, перевести в верхний регистр, а затем развернуть задом наперёд. Вместо того чтобы писать вложенные вызовы, мы можем использовать композицию с |&gt;:</p><ul><li>Мы начинаем с initial_string.</li><li>Оператор |&gt; берёт значение initial_string и передаёт его как первый аргумент функции String.trim().</li><li>Результат String.trim() ("hello elixir world") снова берётся оператором |&gt; и передаётся как первый аргумент в String.upcase().</li><li>Результат String.upcase() ("HELLO ELIXIR WORLD") аналогично передаётся в String.reverse().</li><li>final_result получает итоговое значение этой цепочки — "DLROW RIXILE OLLEH".</li></ul><p>Такие функции проще читать и комбинировать, но при отладке цепочки могут возникать проблемы.</p><h3>Работа с рекурсией вместо циклов</h3><p>В функциональных языках программирования часто приходится работать с рекурсией вместо циклов. Их разница в том, что цикл — это функция, которая повторяет код внутри себя. Рекурсия — функция, которая начинается, завершается и вызывает саму себя, вместо того, чтобы зацикливать содержимое.</p><figure><img src="https://media.tproger.ru/user-uploads/105039/2025-04-15/388125fb-01ac-4a3b-ad10-91a619d9d78f.jpg" alt="" /><figcaption>Спасибо пользователю fesher за идею схемы сравнения рекурсии и цикла</figcaption></figure><p>Она работает как два зеркала, которые поставили друг напротив друга. Одно отображает другое.</p><p>Пример рекурсии в Elixir:</p><p>Этот код выводит числа меньше пяти. Он работает до тех пор, пока переменная n не будет равна нулю.</p><p>В отличие от цикла, функция print_down() в конце вызывает сама себя.</p><p>Рекурсии отлично подходят для задач с вложенностью и хорошо читаются. Но с ними надо быть начеку, так как они могут бесконечно выполняться и переполнять память.</p><p>Иногда в функциональном программировании используют хвостовую рекурсию. Она отличается от обычной рекурсии тем, что рекурсивный вызов является последним действием в функции, и после него не выполняется никаких дополнительных операций. Это позволяет виртуальной машине (в случае Elixir — BEAM) оптимизировать рекурсию, избегая создания новых кадров стека для каждого вызова.</p><p>Здесь используется аккумулятор acc, который накапливает промежуточный результат. Рекурсивный вызов factorial(n - 1, n * acc) — это последнее действие в функции, и после него ничего не происходит.</p><h2>Особенности языка Elixir</h2><h3>Синтаксис, вдохновлённый Ruby</h3><p>Хосе Валими, создатель Эликсира, хотел взять элегантность и простоту Ruby, чтобы разработчики могли писать код, который легко читается.</p><h4>Читаемость и минимум шума</h4><p>Как и Ruby, язык Elixir выглядит чисто — меньше скобок, точек с запятой и лишних символов. Пример с Ruby:</p><p>Пример с Elixir:</p><p>Интерполяция строк с #{} — прямое заимствование из Ruby. Это делает вывод текста интуитивным.</p><h4>Блоки с do...end</h4><p>В Ruby и Elixir есть блоки кода с do...end для функций и управляющих структур:</p><p>Ruby:</p><p>Elixir:</p><p>В обоих языках do...end обозначает тело функции — это привычно для рубистов и делает переход на Elixir проще.</p><h4>Сахарный синтаксис</h4><p>Ruby славится «синтаксическим сахаром» — удобными сокращениями. Elixir тоже этим балуется:</p><p>Ruby — пропуск скобок в вызовах:</p><p>Elixir — короткая запись для однострочных функций:</p><p>Оба языка стараются убрать лишнее, чтобы код был лаконичным.</p><h4>Модули вместо классов</h4><p>В Ruby классы и модули — основа ООП:</p><p>В Elixir модули — это просто контейнеры для функций, без ООП:</p><p>Имена модулей и стиль их использования явно перекликаются с Ruby, хотя и Elixir функциональный язык.</p><p>Среди языков, как Elixir, которые ориентированы на функциональное программирование и конкурентность, мало кто предлагает столь же современный и читаемый синтаксис.</p><h3>Конкурентность через Actor-модель (Erlang VM)</h3><p>Эликсир основан на виртуальной машине Erlang VM. Она поддерживает конкурентность и работу при помощи Actor-моделей.</p><p>Конкурентность — когда мы управляем множеством задач или потоков. Эти задачи могут выполняться параллельно, если у нас многоядерный процессор, либо быстро переключаться на одном ядре, создавая иллюзию одновременности.</p><p>Обычно конкурентные задачи работают с общей памятью. Это может приводить к малозаметным ошибкам наподобие гонки данных или взаимоблокировок. Для таких случаев в Elixir есть Actor-модели.</p><p>Это сущности, которые отвечают за выполнение задач. Акторы в Эликсире изолированы. Каждый процесс — это легковесный актор с собственной памятью:</p><p>Этот код запускает новый процесс, который выведет сообщение и умрет. А теперь представим миллион таких:</p><p>Акторы не дерутся за ресурсы — они отправляют сообщения через send и ждут ответа с receive:</p><p>Благодаря тому, что акторы изолированы друг от друга, у системы высокая отказоустойчивость. Если одни актор отвалится, то не потянет за собой остальных.</p><h3>Распределённые вычисления и масштабируемость</h3><p>Elixir отлично подходит для горизонтального и вертикального масштабирования. Мы можем распределять акторов по разным узлам, которые связаны между собой как одна система:</p><p>Node.connect соединяет два узла. Node.spawn запускает процесс на узле node2@host. Код выполняется там, но мы управляем им с узла node1@host.</p><h3>Встроенная поддержка обработки отказов (fault tolerance)</h3><h4>Изоляция процессов</h4><p>В Elixir задачи выполняются в отдельных процессах. Каждый процесс изолирован: у него своя память, и он не может случайно сломать другие процессы. Если один падает, система продолжает работать.</p><h4>Связывание и мониторинг</h4><p>Иногда нужно знать, что процесс упал, чтобы отреагировать. Для этого есть связывание (Process.link) и мониторинг (Process.monitor).</p><p><b>Связывание </b>соединяет процессы. Если один процесс падает, то другой получает сигнал и тоже падает.</p><p>В этом примере spawn_link связывает процессы. Когда дочерний процесс падает (raise), главный тоже падает, потому что они связаны. Поэтому IO.puts не выполнится.</p><p><b>Мониторинг мягче</b> — мы просто получаем сообщение о сбое, падение процесса не тянет за собой другие процессы.</p><p>Process.monitor(pid) включает наблюдение за процессом. Когда тот падает, мы не падаем сами, а получаем сообщение. Мы его обрабатываем через receive.</p><h4>Supervisor</h4><p>Supervisor — это специальный процесс в Эликсире, который следит за другими процессами и перезапускает их, если они падают. Supervisor позволяет не просто переживать сбои, а автоматически восстанавливаться.</p><ul><li>Demo.start запускает супервизор, который следит за одним процессом.</li><li>start_crasher — запускает процесс, который тут же падает.</li><li>Супервизор автоматически перезапускает его бесконечно.</li></ul><h4>Распределённость и отказоустойчивость</h4><p>Отказоустойчивость Elixir работает не только на уровне одного сервера, но и на уровне кластера. Если процессы на разных узлах и один сервер падает, то нагрузка равномерно распределяется между другими серверами.</p><p>Всё происходит с одного узла (node1) — он управляет и следит. Если процесс на node2 падает (например, узел отвалился или exit(:boom)), то :DOWN ловится через monitor и запускается резерв на node3.</p><h2>Сравнение Elixir с другими языками</h2><h3>Elixir vs Erlang: современный синтаксис против классического подхода</h3><p>У Elixir и Erlang много общего. Во-первых, они оба придерживаются функционального подхода к программированию, во-вторых, у них общая виртуальная машина Erlang VM (BEAM). Именно от неё Elixir получил связанные с отказоустойчивостью фишки вроде акторов.</p><h4>Общий синтаксис</h4><p>Несмотря на то, что между языками много общего, эликсир был разработан с учётом требований к современным языкам программирования, в том числе к синтаксису.</p><p>У Erlang он классический, немного старомодный, с уклоном в функциональный стиль. Много точек, запятых и точек с запятой.</p><p>Elixir читаемый, минималистичный, с закосом под Ruby. Код выглядит чище и ближе к современным языкам:</p><h4>Работа со строками</h4><p>В Erlang строки — это списки символов, каждый «Hello» внутри — это [72,101,108,108,111].</p><p>Тут всё строго: ++ склеивает списки, а io:format с ~s выводит результат. Это работает, но медленно и неудобно, особенно для больших текстов.</p><p>А вот так выглядит код на эликсире:</p><p>Строки в эликсире — это бинарные данные, быстрые и удобные. Оператор &lt;&gt; соединяет их, а |&gt; (pipe) делает код читаемым, как поток: взял «Hello», добавил «world», вывел. Никаких списков и лишних точек.</p><h4>Работа со списками</h4><p>Подход к работе со списками тоже отличается. В Erlang:</p><p>Тут всё вручную: [0|List] добавляет элемент в начало, а ~p выводит.</p><p>В Elixir списки тоже функциональны, но с бонусами:</p><p>У модуля Enum есть готовые функции вроде map, которые экономят время и нервы.</p><h4>Работа со словарями</h4><p>Erlang долго обходился без словарей, а когда они появились (maps), то остались простыми:</p><p>Всё по делу, но без изысков. А вот пример с эликсиром:</p><p>Точечная нотация (map.age), удобный синтаксис и pipe для обновлений.</p><h3>Elixir vs Ruby: почему Elixir лучше подходит для высоконагруженных систем</h3><p>Ruby: Использует потоки (threads) или процессы ОС (например, через Puma или Unicorn). Это тяжеловесный подход: каждый поток потребляет память, а глобальная блокировка интерпретатора (GIL в MRI Ruby) ограничивает параллелизм.</p><ul><li>GIL мешает реальной параллельности на одном ядре.</li><li>Для масштабирования нужны новые сервера и внешние инструменты (Redis, Sidekiq).</li></ul><p>У Ruby нет такой отказоустойчивости и имутабельности. В конце концов, это интерпретируемый язык с одним потоком выполнения в MRI, а значит, у него невысокая производительность.</p><h3>Elixir vs Go: конкурентность через процессы BEAM против горутин</h3><p>Процессы в Elixir благодаря акторам изолированы друг от друга, у них нет общей памяти и поэтому они более отказоустойчивые. Из-за изоляции один процесс весит от 300 кб.</p><p>У Go есть свои аналоги акторов, они называются горутины, но работают немного иначе. Гоурутины не изолированы между собой, у них общая память и они «дешевле». Один горутин весит от 2 кб., вместо 300 кб. как у Elixir.</p><p>Благодаря этому горутины лучше работают с памятью и более производительны в сравнении с акторами. Однако из-за отсутствия изоляции приходится заморачиваться с их синхронизацией. Такая система менее отказоустойчивая. Go лучше подходит для проектов, где важнее всего производительность, Elixir выигрывает там, где нужна стабильность.</p><h2>Примеры кода на Elixir</h2><h3>Определение модуля и функций</h3><p>Модули — это «коробки», в которые мы помещаем функции. Так проще организовать код:</p><ul><li>defmodule создаёт модуль, внутри него — функции с def.</li><li>do...end — для многострочных функций, do: — для коротких.</li><li>IO.puts — это как print, выводит результат в консоль.</li></ul><h3>Использование pipe (|&gt;) для удобного комбинирования операций</h3><p>Pipe-оператор (|&gt;) — это фишка Elixir, которая делает код читаемым, передавая результат одной операции в следующую.</p><p>|&gt; берёт результат и передаёт его как первый аргумент следующей функции.</p><p>Вместо вложенных вызовов (String.reverse(String.trim(String.upcase("elixir")))), мы пишем шаги сверху вниз.</p><h3>Асинхронные процессы через spawn и GenServer</h3><ul><li>spawn запускает новый процесс, который работает параллельно.</li><li>pid — это идентификатор процесса, его можно использовать для общения между процессами.</li><li>Процессы изолированы: один спит, другой идёт дальше.</li></ul><h2>Где применяется Elixir?</h2><h3>Веб-приложения с высокой нагрузкой (Phoenix Framework)</h3><p>Фреймворк Phoenix, построенный на Elixir, идеален для веб-приложений, которым нужно обрабатывать тысячи или миллионы пользователей одновременно. Вот пара примеров его использования:</p><p>Bleacher Report: Этот спортивный сайт, их аудитория превышает 200 млн человек. В 2017 году <a href="https://www.erlang-solutions.com/case-studies/bleacher-report-case-study/">его перенесли</a> на Phoenix, чтобы улучшить производительность и справиться с пиковыми нагрузками во время крупных событий. Phoenix позволил  эффективно использовать веб-сокеты для интерактивных функций, по типу обновлений в реальном времени.</p><p>Pinterest: Одна из крупнейших социальных платформ, которая <a href="https://www.monterail.com/blog/famous-companies-using-elixir">использует </a>Elixir для управления трафиком и отправки уведомлений с 2014 года. Благодаря Elixir компании удалось повысить производительность системы уведомлений до 14 тыс. в секунду. Также получилось сократить количество серверов для этой задачи с 30 до 15.</p><h3>Чаты и мессенджеры</h3><p>Elixir отлично подходит для real-time приложений, где важна мгновенная доставка сообщений и высокая отказоустойчивость:</p><p>Discord: Популярная платформа для геймеров и сообществ <a href="https://www.monterail.com/blog/famous-companies-using-elixir">использует </a>Elixir для обработки миллионов сообщений в реальном времени. Каждый пользователь или чат работает как отдельный процесс, что позволяет системе легко масштабироваться.</p><p>WhatsApp: В основном работает на Erlang, но Elixir <a href="https://www.netguru.com/blog/10-companies-use-elixir">здесь тоже присутствует</a>. Он помогает обрабатывать миллиарды сообщений каждый день.</p><h3>Микросервисная архитектура</h3><p>Elixir используется в микросервисах — подходе, где большие системы разбиваются на независимые сервисы. Вот несколько примеров:</p><p>Financial Times: Известное издание <a href="https://www.erlang-solutions.com/blog/which-companies-are-using-elixir-and-why-mytopdogstatus/">перешло </a>на Elixir для своего GraphQL API, чтобы повысить производительность и обеспечить стабильность при росте числа читателей.</p><p>Lonely Planet: Этот туристический ресурс <a href="https://www.erlang-solutions.com/blog/which-companies-are-using-elixir-and-why-mytopdogstatus/">использует </a>микросервисы на Elixir для управления контентом, что позволяет быстро обновлять данные и масштабировать систему под миллионы пользователей.</p><p>PepsiCo: Компания <a href="https://www.erlang-solutions.com/blog/which-companies-are-using-elixir-and-why-mytopdogstatus/">применяет </a>Elixir в проектах eCommerce и IoT, где микросервисы помогают эффективно обрабатывать данные и взаимодействовать с клиентами.</p><p>Изолированные процессы и встроенные инструменты для распределённых систем делают Elixir надёжным выбором для микросервисной архитектуры.</p><h3>Интернет вещей (IoT) и распределённые системы</h3><p>Elixir также нашёл своё место в IoT, где важны стабильность и управление множеством устройств:</p><p>Nerves: Это открытый проект, который позволяет создавать прошивки для IoT-устройств на <a href="https://nerves-project.org/">Elixir</a>.</p><p>TeslaMate: Приложение, <a href="https://www.balandavid.com/en/posts/2023/01/elevate-your-tesla-experience-with-teslamate/">написанное </a>на Elixir, собирает данные с автомобилей Tesla в реальном времени, предоставляя владельцам подробную статистику. Оно демонстрирует, как Elixir справляется с обработкой данных от распределённых источников.</p><p>В IoT каждое устройство может работать как отдельный процесс, а супервизоры обеспечивают стабильность, даже если одно из устройств временно отключается.</p><p>Ты точно программист, если читаешь это! Больше мемов, инсайтов и боли кодеров <a href="https://t.me/+ajgz7pDecB4xZTI6">тут</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Kotlin: как работают корутины и многопоточность? Уровень — middle</title>
      <link>https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle</link>
      <comments>https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle</guid>
      <description><![CDATA[<p>Проходи квиз, чтобы узнать, как хорошо ты шаришь в Котлине. С нас — почет и уважение</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle">Kotlin: как работают корутины и многопоточность? Уровень — middle</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Kotlin]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 18 Jan 2025 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сегодняшний квиз не совсем для новичков. Многопоточность и корутины — уже скорее для уровня джун+/мидл. Хотя… Если вы настолько уверены в своих навыках, то попробуйте их проверить и доказать, что вы прирожденный Kotlin-разработчик. А если хотите немного освежить знания, читайте наши статьи:</p><ol><li><a href="https://tproger.ru/articles/kotlin-i-funkcionalnoe-programmirovanie--berite-luchwee">Kotlin и функциональное программирование: сделайте код лучше</a></li><li><a href="https://tproger.ru/articles/perehod-s-java-na-kotlin-pri-sozdanii-mobilnogo-prilozhenija">Переход с Java на Kotlin при создании мобильного приложения</a></li><li><a href="https://tproger.ru/translations/10-lajfhakov-dlja-android-razrabotchika-poleznye-extensions-na-kotlin">10 лайфхаков для Android-разработчика: полезные extensions на Kotlin</a></li></ol>]]></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>Kotlin и функциональное программирование: сделайте код лучше</title>
      <link>https://tproger.ru/articles/kotlin-i-funkcionalnoe-programmirovanie--berite-luchwee</link>
      <comments>https://tproger.ru/articles/kotlin-i-funkcionalnoe-programmirovanie--berite-luchwee?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Ирина Тюльпакова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kotlin-i-funkcionalnoe-programmirovanie--berite-luchwee</guid>
      <description><![CDATA[<p>Урс Питер на KotlinConf 2023 объяснил, какие принципы сделают код функциональнее, рассказал про монады, контейнеры и библиотеку Arrow.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kotlin-i-funkcionalnoe-programmirovanie--berite-luchwee">Kotlin и функциональное программирование: сделайте код лучше</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Kotlin]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 11 Mar 2024 14:42:11 GMT</pubDate>
      <content:encoded><![CDATA[<p>Урс Питер, сеньор software developer поделился лучшими практиками программирования на Kotlin.</p><p>В видео он рассказал:</p><ol><li>В чём заключается основные принципы императивного и ориентированного на выражения программирования.</li><li>Что такое монады и как их использовать.</li><li>О пользе скоуп функций, функций высшего порядка и композитных функций.</li><li>Представил Arrow - библиотеку для Kotlin, которая реализует концепции категорной теории и предлагает расширенные возможности для функционального программирования.</li><li>Продемонстрировал, что важно выборочно использовать монады, так как они могут как улучшить, так и усложнить код.</li></ol><p>Ниже — транскрибация ролика на русском языке.</p><p>Что ж, моя презентация будет посвящена функциональному программированию и Kotlin. Краткое введение о себе.</p><p>Я Урс Питер, сеньор software developer. «Днём» я работаю по специальности. Также  являюсь тренером в Академии Xebia, где помогаю разработчикам на всех этапах в Kotlin от новичка до среднего и продвинутого уровня, что довольно интересно, потому что я сталкиваюсь со многими средами и вижу, как Kotlin используется разными способами. Я также сертифицированный тренер Chatbrain. Карьеру я начинал около 20 лет назад с Java, затем около 10 лет назад перешел на Scala, вложил значительные средства в экосистему Scala, также был одним из первых сертифицированных тренеров, написал множество приложений на Scala, создал учебные материалы.</p><p>Но уже примерно четыре-пять лет я пишу исключительно серверную часть, по крайней мере, на Kotlin. К чему я это? К тому, что именно опыт работы со Scala побудил меня выступить с этим докладом. И это во многом связано с этим графиком.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/83fc7abf-1b19-4023-b083-5fcdff251d88.png" alt="" /></figure><p>Итак, когда вы смотрите на временную шкалу, в 2017 году Kotlin начал стремительно расти, но в то же время начался спад Scala. И вопрос, конечно, в том, почему это так? Нет ни одной резонной причины, но я думаю, что в этой презентации я коснусь довольно важных деталей, почему это произошло. И, конечно, мы не хотим, чтобы это повторилось с Kotlin.</p><p>&lt;...&gt;</p><h2>Стили программирования</h2><p>Тогда, что ж, давайте отправимся в путь и начнем наше путешествие, которое, надеюсь, будет преобразующим. И начнем мы с болот императивного программирования. &lt;...&gt;</p><p>Почему? если бы вы классифицировали стили программирования, которые у нас есть, и выделили бы только две большие группы, мы могли бы сказать, что, с одной стороны, у нас императивное программирование. Оно в основном основывается на объявлениях переменных, которые затем изменяются по ходу дела.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/e61b4b90-8505-4b18-a48f-577fb74fe657.png" alt="" /></figure><p>Как вы видите в этом конкретном примере. Языковая функция, которую мы используем в Kotlin для реализации этого стиля, — это var`s, циклы, множество изменяемых вещей, такие как изменяемые коллекции, изменяемые объекты, операторы return.</p><p>Итак, как только мы достигли определенного этапа, мы думаем: хорошо, теперь все достаточно хорошо. Это императивное программирование.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/40b8b4ef-d6a3-4fdf-9421-4eef50b657f0.png" alt="" /></figure><p>Напротив, у вас есть вторая категория, которая является программированием, ориентированным на выражения (Expression-Oriented Programming). Это еще не функциональное программирование, но оно лежит в его основе. И программирование, ориентированное на выражения, больше опирается на мышление функциями с точки зрения ввода и вывода. Функции, которые мы используем, — это более неизменяемые функции Kotlin, такие как классы данных, неизменяемые коллекции.</p><p>И конструкции, которые мы стараемся использовать, являются конструкциями выражений, что означает, что мы всегда ищем концепции, которые возвращают значение, а не только производят побочный эффект. И это, конечно, также относится к функциям более высокого порядка.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/038e89d2-849e-4e5a-9d46-98a04315fb90.png" alt="" /></figure><p>Примеры, которые вы видите здесь, в точности совпадают. Они дают точно такой же результат. Итак, что же на самом деле является фундаментальным в этих двух стилях программирования?</p><p>Что ж, императивное программирование опирается на изменяемые данные наряду с побочными эффектами. Именно так мы думаем о нашей логике программирования, в то время как программирование, ориентированное на выражения… Оно как бы поддерживает представление о преобразованиях данных на основе некоторого ввода, который затем передается в некоторый вывод, и этот конкретный ввод может снова служить входными данными для следующего преобразования. Это действительно фундаментально для этих двух стилей. Итак, как только я лично начал осваивать экспрессионно-ориентированное программирование, я действительно стал лучше как программист, в том смысле, что я написал более качественное программное обеспечение, которое лучше поддерживается, более надежное и так далее.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/b6da6d4c-b0ed-4a49-9fa7-490d81a9d423.png" alt="" /></figure><p>Почему это так? Ну, это довольно легко обосновать. Итак, во-первых, когда вы просто смотрите на приведенный здесь пример, там меньше строк, он более лаконичный. Это также более понятно, потому что в основном мы программируем на более высоком уровне абстракции. Вместо циклов и изменения переменных мы теперь очень четко выражаем наши намерения, говоря, что мы фильтруем, мы максимизируем. Таким образом, это придает нашему коду больше семантики.</p><p>Поскольку у нас есть ввод-вывод, мы также более детерминированы, верно? Потому что мы получаем выходные данные, а когда у вас есть выходные данные, нам легче их протестировать. И что также интересно отметить, так это то, что при программировании, ориентированном на выражения, наши блоки области видимости (scope blocks) меньше, чем при императивном стиле.</p><p>В императивном примере вы видите, что мы объявляем эту переменную devs сверху, а затем на протяжении всей процедуры мы используем её. Таким образом, область применения довольно велика, в то время как в стиле, ориентированном на выражение, у нас в основном есть только функция, которая является областью применения, а затем мы переходим к следующей области применения, что также значительно упрощает рассуждения о разработке программного обеспечения, о нашем программном обеспечении. Итак, если вы еще не полностью освоили этот стиль программирования, ориентированный на выражение, то я советую сделать это.</p><p>Попробуйте хотя бы две недели. И потом, я совершенно уверен, что если вы по-прежнему будете использовать более императивный стиль, вы от него уже не уйдёте. Потому что когда вы выберетесь из болот императивного программирования, вы окажетесь в плодородной долине программирования, ориентированного на выражения. Ладно, пока все хорошо, что ж, вы можете подумать, что звучит все это мило, но реальный мир не всегда так идеален, как мы только что описали, потому что у нас есть много API, которые все еще очень изменчивы, так как же мы можем справиться с этими ситуациями?</p><h2>Функции области видимости (scope functions)</h2><p>Сначала давайте посмотрим на пример, чтобы увидеть, что мы более или менее делаем. Итак, очевидно, мы инициализируем некоторые клиенты REST, а также назначаем некоторые свойства для выполнения нашего вызова. Затем мы что-то делаем с файлом.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/d52f6874-6c7f-4db3-821d-175e360fdc73.png" alt="" /></figure><p>В конечном итоге мы извлекаем данные с помощью этих REST-клиентов, которые затем выводим в какой-нибудь CSV-файл, и в конце концов закрываем наш ресурс. И поскольку эти API изменяемы, вы можете подумать «хорошо, единственный способ, которым я могу решить эту проблему, — это объявления переменных, а затем попутно менять их». Ну, это не совсем так.</p><p>Потому что в Kotlin есть эти замечательные функции области видимости, и, применяя их, мы можем добиться большего. Это был бы пример со скоуп функциями. Итак, что мы получаем с помощью скоуп функций?</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/cbf2df9d-6820-4a5c-afe4-46c3c623e0c3.png" alt="" /></figure><p>Прежде всего, теперь с помощью apply мы группируем все операции, которые ...вызывают изменения в нашем объекте. Таким образом, он, по крайней мере, изолирован. Мы изолируем нашу изменчивую часть. С помощью let мы можем опустить оператор getAll и return. Кроме того, это приятно, потому что теперь мы четко отмечаем побочный эффект.</p><p>Итак, когда вы читаете этот код, вы говорите: "О да, здесь имеет место побочный эффект". Кроме того, пример с файлом и run является прекрасным примером изоляции в том смысле, что мы группируем операции с файлом в блок, но мы даже не раскрываем файл. В примере слева у нас была файловая переменная, которую мы можем использовать везде. Поэтому, когда мы позже проведем рефакторинг, вы можете легко сделать что-то с файлом, случайно весь его изменить, в то время как справа это действительно применимо только в этом блоке, а не за его пределами.</p><h2>Функции высшего порядка (higher-order functions)</h2><p>Итак, что мы получаем с помощью этих скоуп функций, так это способ как бы соединить императивный мир с миром, более ориентированным на выражение. Итак, имея это в виду, я думаю, мы продвинулись немного дальше в нашей плодотворной долине. Но что ж, когда вы оглядываетесь вокруг, горы все еще есть, верно? И мы еще не там.</p><p>Мы все еще в долине. Итак, давайте посмотрим, сможем ли мы подняться наверх, чтобы полюбоваться прекрасным видом. Итак, мой клиент подходит к моему столу и спрашивает: "Приятно, что вы выводите мои данные в CSV, но мне нужен гораздо более навороченный формат. Я также хочу иметь TSV, отдельные значения в табуляции.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/e394f1a8-bef5-4f63-b4ee-a51ff922842d.png" alt="" /></figure><p>Вот как мы могли бы решить эту проблему, верно?</p><p>Что, конечно, довольно глупо. Мы просто продублировали все это, изменили наши выходные данные на TSV и создали проблему с ремонтопригодностью. Итак, как вы можете лучше решить ее?</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/2cd6563e-a3ed-49e7-9c3e-6982c4c50961.png" alt="" /></figure><p>Это довольно просто сделать. Итак, что мы можем сделать в первую очередь, так это определить часть, которую мы дублировали. И эта дублированная часть, по сути, становится нашей общей структурой управления. Затем мы идентифицируем изменяющуюся часть.</p><p>И мы очень внимательно смотрим на то, что входит и что выходит. И эту часть мы выводим в функцию. А затем используем эту функцию в указанном месте, чтобы, ну, теперь быть очень гибкими. Итак, в этом преимущества функций более высокого порядка. Я часто вижу, что функции более высокого порядка используются в коллекциях и, возможно, в других API, но я не всегда вижу, как они используются в вашем собственном бизнес-коде.</p><p>В то время как есть много случаев, когда у вас есть какая-то структура управления, которую вы хотите повторно использовать и просто подправить определенную ее часть, именно здесь функции более высокого уровня действительно могут отлично справиться. Как пример в тестировании, с которым я часто сталкиваюсь. Тестирование часто не является тем местом, где вы применяете лучшие инженерные практики, поэтому дублирование не считается таким злом, как в реальном бизнес-коде.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/94f49ad1-11d0-4e43-b14b-308e72898028.png" alt="" /></figure><p>Итак, вы видите этот код довольно часто, по крайней мере, там, где я бываю. Например, здесь мы создаем некоторые тестовые данные, а затем очищаем их. Вот пример использования, который у нас здесь есть.</p><p>И здесь применим тот же трюк, выводим общую структуру управления, преобразуем изменяющуюся часть в функцию, и тогда мы получаем два преимущества. Прежде всего, у нас было повторное использование. Мы даже можем сделать нашу функцию немного настраиваемой, например, сказать, что я хочу создать так много элементов для тестирования.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/f98ff4f3-8c1f-4eaa-bea7-d15fcbbdf7b5.png" alt="" /></figure><p>Итак, это, во-первых, повторное использование, а во-вторых, также наши тесты становятся более выразительными, потому что теперь мы можем сосредоточиться на условиях тестирования, а не на всей аппаратуре, необходимой нам для получения определенного фрагмента данных в состоянии, при котором мы можем его протестировать.</p><p>Хорошо. Итак, как только мы освоим функции высокого порядка, мы окажемся на изобильной высоте функций высокого порядка, которая все еще не достигла вершины.</p><h2>Композитные функции (composable functions)</h2><p>Что ж, если вы внимательно посмотрите на эти иерархические функции, которые мы только что создали, то чего нам все еще не хватает, так это какой-то абстракции, чтобы объединить их вместе для создания новых ценных результатов, верно? Итак, чего нам, по сути, не хватает, так это того, что называется композиционностью.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/bec83b0e-eaad-40b1-aa7e-576b5ae52754.png" alt="" /></figure><p>Эти вещи не складываются. Когда мы смотрим на самый первый пример, который мы видели в начале выступления, с коллекциями, мы видим, что это прекрасно складывается, не так ли? Итак, у вас есть коллекция, затем у вас есть эти функции более высокого порядка, которые вы применяете, вы выполняете свои преобразования, пока не получите желаемый результат.</p><p>Очевидно, контейнер коллекции или объект коллекции для сбора, вместе с функциями более высокого порядка, дает нам эту возможность. Так что было бы довольно интересно сделать еще один шаг вперед и взглянуть на фундаментальные аспекты, которые позволяют нам поддерживать композиционность.</p><p>Потому что, если бы мы могли как бы вывести эти существенные аспекты, мы смогли бы создавать наши собственные структуры данных помимо коллекций, которые мы можем составлять.</p><h2>Теория категорий</h2><p>Что ж, и если вы попытаетесь достичь этого, то вот куда вы направляетесь, в область теории категорий. Что такое теория категорий? Теория категорий — это теория математических структур и их отношений. Мне нравится сравнивать ее с периодической таблицей Менделеева в физике, знаете, где у вас есть все атомы, и все атомы вы также можете объединить в молекулы, которые обладают определенными чертами и характеристиками.</p><p>И третья категория — это нечто похожее, но с логическими строительными блоками, которые вы можете комбинировать, пока не получите определенные характеристики. И, как вы можете видеть, это не высота, не холм, это гора. А в горах можно подняться очень высоко, но можно и упасть очень глубоко. Итак, это опасная зона, где внезапно появляются все эти модные слова. Ну, а почему это опасная зона?</p><p>Я думаю, во многом это потому, что терминология, используемая в категориальной теории, которая не обязательно имеет отношение к разработке программного обеспечения, просто используется в разработке программного обеспечения, потому что это хорошая область ее применения. Но эти термины действительно своего рода притянуты за уши.</p><p>&lt;...&gt;</p><h2>Контейнеры</h2><p>Хорошо, итак, когда я спросил в начале, кто слышал слово «монада», поднялось довольно много рук, но определенно не все, верно? Приятно то, что мы все используем монады, и даже на ежедневной основе.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/7e81dabd-0710-4338-beed-c67fc7c693dc.png" alt="" /></figure><p>Очевидно, вы используете его, сами того не подозревая. Мне нравится думать о монадах по-разному, и есть разные способы, этот способ помогает мне лучше всего — думать о них как о контейнерах. Что такое контейнер? Ну, контейнер — это то, что содержит предметы. Интересная особенность контейнеров в том, что у вас есть разные виды контейнеров. У вас есть герметичные контейнеры, у вас есть контейнеры с кондиционером, у вас есть безопасные контейнеры. Несмотря на то, что контейнеры содержат что-то общее, они все равно могут быть разной формы. Итак, что мы попытаемся сделать сейчас, так это взглянуть на контейнер коллекций. Коллекции также содержат элементы, верно?</p><p>И на что мы действительно смотрим, так это на то, что такая коллекция делает полезной с точки зрения композиции? Итак, не все эти функции высшего порядка, но с точки зрения компонуемости, каковы ключевые характеристики, которые делают это возможным? Хорошо, давайте начнем с первого шага. И все это довольно просто, вы можете быть удивлены.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/5211ee01-f4b9-4988-ad37-8796d10f7fef.png" alt="" /></figure><p>Вероятно, только при сочетании вещей они становятся немного сложнее. Итак, первый пример, который вы видите, — это то, что мы можем комбинировать коллекции. Когда у нас есть контейнер с элементами, и мы объединяем его с другим контейнером с элементами, мы получаем новый контейнер, содержащий все элементы предыдущих. И когда вы затем переходите к этой категории, горе, скала, тогда можно назвать это моноидом.</p><p>У монады есть две характеристики. Она может создавать пустую вещь. Вы можете создать пустой список, верно? И вы можете объединять списки. Но когда вы объединяете списки, вы не получаете как бы два контейнера. Нет, вы по-прежнему получаете один контейнер, который содержит события другого. И просто чтобы сделать это немного сложнее, в основном, комбинируемая часть называется полугруппой.</p><p>Но ладно, пока давайте просто придерживаться термина “монада” (я бы предпочел термин "комбинируемый"; тогда я бы вроде как понял, о да, вы комбинируете разные вещи).</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/436c6503-226f-4c6d-9447-7130fafc4317.png" alt="" /></figure><p>Итак, это первая характеристика. Вторая заключается в том, что вы можете преобразовывать элементы коллекции. У нас снова есть контейнер, содержащий элементы. Мы берем каждый элемент, применяем к нему определенную функцию, результатом чего затем будет новый контейнер с преобразованными элементами. И если у вас есть такая возможность, то объектам удается вызвать это функтором. Итак, когда у чего-то есть метод map, это функтор. «Сопоставимый» (mappable) для меня звучало бы более логичным.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/b7f72368-45c3-4ce2-86aa-d5b6c8dbbc6b.png" alt="" /></figure><p>Хорошо, тогда третий вопрос, который, вероятно, самый важный, самый запутанный, заключается в следующем. Давайте посмотрим на эти простые домены, у меня есть разработчик, у которого есть языки, и если я хочу знать все языки нескольких разработчиков, тогда, если я буду использовать map, я получу этот список списков, верно? Но, в конце концов, я просто хочу иметь список языков.</p><p>Итак, что я должен применить? Вот тут-то и появляется flatMap, так что же делает flatMap? Каждый элемент, который вы извлекаете из контейнера, снова преобразуется в контейнер, но результирующий контейнер как бы выравнивает (убирает) дополнительный контейнер, который мы добавили.</p><p>Все в порядке, вы получаете только один список с элементами. И если вы затем объедините характеристики, которые мы видели раньше, то есть моноид и функтор, в сочетании с flatMap, то та-да, вот и наша монада. Итак, я сказал, что монада — это комбинация моноида, который я сейчас называю комбинируемым, функтора, отображаемого аспекта и монады, я бы предпочел термин «компонуемый», потому что это то, что вы можете делать с этими вещами. Хорошая ли идея дать этому новую терминологию?</p><p>Честно говоря, я думаю, что это плохая идея. Поэтому я думаю, забудьте об этом, придерживайтесь моноидной строки и так далее. Но, ну, определенно, не помогло то, что они использовали такого рода терминологию. И также здесь, на самом деле, больше уровней абстракции. Моноид идёт от «применимый» (applicative) и так далее. Есть также небольшие нюансы, но пока давайте просто придерживаться этих трех. Итак, мы все знаем монады, верно?</p><h2>Монады (Monads)</h2><p>Потому что вы используете коллекции. И я уверен, что вы знаете больше монад. Например, optional. Что такое optional?</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/f85fd8ef-d45a-42d3-b542-2a9975c3bc4f.png" alt="" /></figure><p>Если вы попытаетесь определить, что делает монада, есть какое-то особое житейское определение, которое вы можете применить к ней. Которое выглядит следующим образом: «Опциональность моделирует эффект опциональности» («an optional models the effect of optionality»). Итак, эффект опциональности.</p><p>Это очень важная часть, и мы также увидим позже, что это слово будет повторяться. Итак, давайте посмотрим, является ли опциональный параметр монадой? Давайте проверим. Есть ли у нас пустой метод? Мы можем создать пустой опциональный параметр, который является числом, верно? Можем ли мы сопоставить наши значения?</p><p>Да, мы можем. Итак, если опциональный параметр содержит значение, мы можем сопоставить его. И если он пуст, он ничего не делает. Здесь вы видите эффект сопоставления.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/e5c54d69-b009-4e64-b40e-f2a0096f3b36.png" alt="" /></figure><p>Отличается от коллекции, но это все равно map с другим эффектом, то же самое верно и для flatMap, поэтому, если на flatMap я создам наши опции, я не получу опциональный набор опций (option of optionals), потому что один слой будет выровнен… Кто занимался Reactive программированием с фьючерсами, всеми этими абстракциями? Да, хорошо, круто, довольно много. Там тоже есть монады.</p><p>Что они делают? Ну, моноблок или реактивный блок моделирует эффект синхронности. Можем ли мы создать пустой?</p><p>Можем. Итак, как здесь появляется map? Что делает map? Здесь у нас есть некоторый API, который возвращает моноид (mono), и map в основном будет вызван, как только асинхронная обработка будет завершена, и мы получим значение «окей». Здесь вызывается map.</p><p>То же самое верно и для flatMap, поэтому, если я хочу последовательно вызывать реактивный блок, то есть сначала API 1, затем API 2, я должен использовать flatMap, потому что в противном случае я получил бы моноид из моноида. Итак, вы также видите здесь, что эффект map и flatMap полностью отличается от других монад, которые мы видели до сих пор, но это все равно map и flatMap.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/baaa7503-6dbb-4199-a581-e81815bc1437.png" alt="" /></figure><p>Так для чего же хороши эти контейнеры монад? Ну, все эти абстракции, я действительно думаю, что плохое в этом смысле уже произошло, так что вы не можете повернуть время вспять, но в основном это просто отпугнуло людей. Это недостаток. Но что вы получите, если пройдете через это, так это, ну, возможность компоновки. И поскольку мы знаем, что можем компоновать материал, что ж, давайте попробуем это.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/ceb04bd5-31e3-4786-9907-0d6637761b0b.png" alt="" /></figure><p>Давайте составим что-нибудь. И хорошим примером использования компонуемости может быть обработка ошибок. У вас здесь есть эти перехваты попыток, и, что ж, на самом деле это некомпонуемая конструкция, верно? Своего рода языковые функции, которые нельзя комбинировать. Просто они должны использоваться несвязанно, по-разному.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/1fc24de4-2ff8-425b-81fa-6a98c71aa483.png" alt="" /></figure><p>Ну, если мы хотим что-то скомпоновать, тогда нам нужна монада, и хорошей новостью является то, что в стандартной библиотеке Kotlin уже есть такая монада, которая называется result и выглядит результат. Result моделирует эффект наличия значения «окей» или исключения, и мы можем применить результат выглядит следующим образом: у него есть map и flatMap, так что, по-видимому, это монада. Это был код раньше, теперь мы используем runСatching в клиентском коде, который затем не вернет нашему разработчику результат либо хорошего значения, либо исключения, и с этого момента мы можем начать компоновать. Это довольно хороший вопрос, не так ли? Итак, у нас есть этот клиент GetDevByName. Откуда мы знаем, что можем ожидать исключения? Этого нет в подписи. Контракт не говорит мне, что есть исключение. И это очень интересная дискуссия, которая, в некотором смысле, то, что мы здесь видим, не является прозрачным.</p><p>Мы можем вернуть то, чего нет в нашем контракте. И функциональным людям это не нравится. Они считают, что прозрачность важна.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/7bbdd266-cb72-4176-bf0e-73e3704be027.png" alt="" /></figure><p>Напротив, довольно интересно, что в Kotlin есть только непроверямые (unchecked, те, которые не отлавливаются компилятором  — прим.переводчика) исключения. Так что, по-видимому, есть некоторые, да, и это немного не согласовано. Не будет рассуждать, хорошо это или плохо. Так что давайте просто продолжим исследование, и я думаю, тогда мы сами придем к выводу. Предположим, что мы хотим быть прозрачными. А если мы хотим быть прозрачными, тогда мы должны быть откровенны. Итак, мы должны четко отметить, что этот метод также может выдать вам исключение посредством результата. И что ж, это то, что мы должны сделать. Таким образом, все наши методы теперь относятся не к языку как к возвращаемому термину, а к результату языка. Тогда мы можем творить нашу композиционную магию. Хорошо, давайте расширим наш пример и используем третий вызов API.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/c8763e6e-a986-4d19-a3fe-f57b87a67fae.png" alt="" /></figure><p>И если мы попытаемся вызвать эти три API последовательно, сначала один, используя выходные данные в качестве входных данных, то в итоге мы получим вот такой код. Таким образом, задействовано много flatMaping. В начале я сказал, что бываю во многих местах, где обучаю разработчиков Kotlin, но объяснять это, особенно тому, у кого очень сильный опыт работы с императивами, довольно сложно. Я имею в виду, что коллекции плоских отображений уже немного сложно. Но контекст flatMaping отличается от контекста других структур? Я имею в виду, это не так уж сложно, но все равно, ощущения не очень приятные.</p><p>Итак, вопрос, конечно, в том, есть ли способ лучше? Я думаю, сейчас тот момент, когда я представляю вам Arrow.</p><p>Что такое Arrow? Arrow — это действительно красиво созданный API, который фактически реализует всю эту теорию, категорию, парадигму, а также предлагает большое разнообразие монад. Я действительно считаю это жемчужиной, что не всегда было так, потому что вначале это был порт из Scala. В Scala есть этот материал в большем количестве, довольно много библиотек доходят до крайности с такого рода концепциями, и они были перенесены на Kotlin. Самая первая версия была как бы не связана с идиоматическим Kotlin. У вас был Kotlin, а затем у вас был этот функциональный мир, и вам действительно приходилось выбирать между одним и другим.</p><p>Они не очень хорошо сочетались. И действительно, самое замечательное в том, что сделала Arrow, особенно в релизе, который вышел сейчас, 2.0, заключается в том, что им удалось действительно блестящим образом соединить два мира, так что большая сложность, которая обычно бросалась бы вам в глаза, исчезла, и вы действительно можете воспользоваться преимуществами. Я приведу несколько примеров этого. Итак, в основе Arrow лежит эффект. Если вы помните, мы всегда говорили, что опциональная модель — это эффект чего-либо.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/b3929c5a-8610-4a33-98ae-bcbfec7e973c.png" alt="" /></figure><p>Эффект — это основная вещь, и затем у вас есть монады, которые реализуют определенный эффект, например опциональный. Результат также преобразуется в эффект. У вас также есть either, вы, возможно, слышали об этом типе, either немного мощнее, чем result. Result может содержать только хорошее значение и исключение. Either просто может содержать два значения, может содержать одно значение разной формы. Таким образом, он может иметь значение типа A или значение типа B, и только по одному за раз.</p><p>Обычно правое ассоциируется с хорошим результатом, а левое — с плохим, но это не обязательно так, но часто это применяется именно таким образом. Есть validated, где вы можете выполнить некоторую накопленную проверку &lt;...&gt;. Хорошо, что Arrow также предлагает очень классную функцию, которая называется monad comprehensions. С ней мы можем работать лучше, чем с помощью flatMap.</p><h2>Monad comprehensions</h2><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/37627c63-521a-41f7-9545-afa627b9f03f.png" alt="" /></figure><p>Как это работает? Что важно упомянуть, так это то, что монопонимание — это нечто из функциональных языков, а у функциональных языков для этого есть языковые особенности. В Kotlin нет языковой функции для монокомпонентов, но в Kotlin есть эта функция, литерал которой связан с receiver, верно? Это то, что вы видите здесь, result, и тогда receiver в этом случае будет такой областью действия result, которая для вас как бы скрыта. Но у этого есть некоторые методы расширения, которые являются bind, и этот метод bind затем может вызывать монады внутри вашего блока кода. Итак, здесь вызов bind и то, что делает bind, — это либо дает вам значение, если вы вычисляете правильное значение, и если это было бы исключением, оно просто провалилось бы и просто вернуло результат, содержащий исключение.</p><p>Таким образом, вычисления сразу же прекратились бы. Таким образом, вам не нужна эта вложенность в flatMap для достижения вашей цели. Это действительно блестящий способ по-прежнему использовать потенциал этих монад, но при этом не быть обремененным flatMapping. Я думаю, что это отличный подход.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/1c05be77-3aed-47e9-998c-19e0001ebe06.png" alt="" /></figure><p>Более того, и это также то, что они вам скажут, так что это краткий обзор, заключается в том, что с появлением этих контекстных приемников вы даже можете сделать эти сигнатуры методов еще более совместимыми с идиоматикой Kotlin, в том смысле, что теперь они определяют только базовый возврат потока тип, поэтому языковая статистика и другие эффекты, которые я мог бы получить, как бы четко отделены от, ну, базового типа, использующего эти контекстные приемники. Я также думаю, что здесь это отличный способ быть откровенным, но не так, чтобы это слишком сильно мешало вам. Так что это определенно потрясающая разработка. Ладно, теперь я думаю, что эта компонуемость потрясающая.</p><p>Итак, давайте просто используем все, что можем. И поскольку мой вариант использования требует только более одной монады, почему бы просто не объединить эти вещи вместе? Потому что мне, во-первых, нужно какое-то реактивное поведение. Во-вторых, я хочу, чтобы код был прозрачным. И в-третьих, почему бы не использовать option?</p><p>Итак, вы получаете эти вложенные контейнеры. Если бы вы тогда только попытались вызвать эти три службы последовательно, это то, что у вас получилось бы в итоге.</p><h2>Проблемы...</h2><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/389ec14f-1fb5-4217-897a-40078b2348a5.png" alt="" /></figure><p>И вы можете себе представить, что теперь мы только что ввели небольшую головную боль при обслуживании. Итак, все map и flatMap, которые мы видим здесь, вызываются одинаково, но я сказал, что эффект на самом деле разный. FlatMap монад отличается от map из result, отличается от map из optional, но это все map и flatMap. Это то, что вы видите.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/2aab6daa-f935-4fbb-8bb8-96e9ae9f5ae2.png" alt="" /></figure><p>Так что все не так уж и здорово. Ну да, вы еще не занимались бизнес-логикой, и если вы обнаружите, что пишете такой код, тогда я могу поздравить вас, потому что вам удалось превратить свою кодовую базу в контейнерный терминал, и если вы внимательно посмотрите, где вы находитесь, то это здесь, у этого крана, и вы просто перемещаетесь вокруг этих контейнеров и пытаетесь найти [что-то], заворачивайте и разворачивайте их, чтобы добраться до того, что вам нужно. Потому что в основном мы программируем обеспечение, которое создаем, что действительно интересно — это то, что находится внутри контейнеров, а не сам контейнер. И это именно то, что у меня много раз было со Scala, и я действительно знаю, что у многих других было то же самое, что… Это очень неприятное чувство в том смысле, что я хочу создать бизнес-логику для выполнения моих вариантов использования, но я всегда борюсь только с упаковкой, распаковкой, заглядыванием в эти контейнеры. Итак, мы только что видели, что у вас есть эти представления о монадах, возможно, они и здесь могут помочь.</p><h2>... и их решение</h2><p>Извините, плохие новости. Понимание монад работает только для одного уровня монады. Так что, если у вас есть вложенные монады, это не сработает. Как насчет трансформаторов монад? Что такое трансформаторы монад? Это снова своего рода грязный трюк для преобразования значения внутренней монады во внешнюю. Итак, вместо того, чтобы вызывать map, flatMap, теперь вы можете вызвать mapT. И тогда вы преобразовали бы комбинацию mono result напрямую в convert result. Ну, это работает только для двух уровней.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/35849873-bc84-4de5-8fc2-415d38d8dc6c.png" alt="" /></figure><p>У нас их три, верно? И вы все равно не можете использовать для этого что-то более всеобъемлющее. И поверьте мне, если вы пойдете по этому пути, то получите такого рода методы: mapТ, mapТ, mapТ, внешняя flatMap, внутренняя. Я написал такой код.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/3072db9a-c4e5-4680-99fa-d319956ef5fd.png" alt="" /></figure><p>Как только это было сделано и мой тест сработал, на следующий день код перестал работать, не говоря уже о том, что мои коллеги не поняли, что происходит. Это действительно не то, что вы хотели бы получить. Итак, совсем не весело. Хорошо, значит, монада совсем не хороша? Что ж, если вы ограничитесь одной монадой, я думаю, у вас все будет в порядке.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/98aec687-0046-4fb9-861a-d3ce3ccd6eb8.png" alt="" /></figure><p>Но как я могу свести три к одному? Итак, давайте попробуем. Я видел, как некоторые из вас все еще поднимают руки, которые использовали эти реактивные строительные блоки. Кто их использует до сих пор? Хорошо, не многие. Я думаю, это хорошо, потому что, если вы это сделаете, я действительно настоятельно советую вам взглянуть на со-программы. Они могут значительно облегчить вашу жизнь, но при этом дают вам точно такие же характеристики, которые вы получаете с помощью этих реактивных строительных блоков.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/cf5a2d1d-6b21-4b42-a9a8-97b5e9ea6f10.png" alt="" /></figure><p>Так что настоятельно рекомендую обратить на них внимание. Итак, мы преобразуем их в spend и можем выбросить наш mono. Второй, ну, вариант. Я думаю, у нас уже есть эта конструкция в Kotlin, верно, которая является nullability. Зачем вводить другой тип конструкции для использования возможности обнуления? Вы же не собираетесь использовать необязательный параметр Java в своем коде, верно?</p><p>Во всяком случае я не советую вам так делать. Просто придерживайтесь одной концепции опциональности, которая в Kotlin ещё и nullability, и все. Хорошо, и как только мы это сделаем, я думаю, наш код снова станет в некотором роде управляемым. И если вы используете let или если вы затем используете другие гласные, которым вы присваиваете свои результаты, это зависит от вас. Но тогда с пониманием результата, комбинацией с методами привязки и приостановки, возможностью обнуления лучше подойдёт. Ладно, я думаю, теперь мы прошли через этот монадический туман, просто взглянули на вершину.</p><p>Если бы я остановился прямо сейчас, я думаю, мы все равно были бы не очень довольны, верно? Должен же быть какой-то синтез целого. Итак, давайте попробуем, если у вас получится лучше. Что на самом деле подводит нас к следующему вопросу: какая монада хороша для вашего кода? Что подводит нас к более широкому вопросу: что такое хороший код?</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/14818146-09db-4c24-be97-0f8ee3fc8405.png" alt="" /></figure><p>Что ж, об этом написано много книг, и еще много книг будет написано на эту тему. Мне хотелось бы, чтобы все было довольно просто. Пока что я видел, что хороший код отражает область, в которой вы находитесь. Итак, если вы зоомагазин, то речь идет о кошках, собаках, собачьем корме. Если вы финансовое учреждение, то речь идет об ипотеке, акциях и так далее. Другими словами, недоменные абстракции, которые вы используете в своем коде, на самом деле не выходят за пределы вашего домена, являются частью абстракций. И если у вас есть эти недоменные абстракции, что ж, тогда они действительно служат очень специфической, полезной службе. Другими словами, если ваш заказчик просматривает ваш код и понимает его, это то, что я считаю хорошей основой для создания кода, который будет поддерживаться многими поколениями, которые придут после вас. Другими словами, если ваш код выглядит как контейнерный терминал, это явно не очень хороший код.</p><p>За исключением случаев, когда ваша компания является контейнерным терминалом, верно? Тогда можно называть вещи «контейнерами» и менять их местами. Итак, теперь то, что я хотел бы сделать, это как бы дать вам представление о том, что произойдет, если вы просто добавите монады ко всему. Как это повлияет на цепочку вызовов вашего приложения.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/a289edf7-a442-44da-96d7-c109fc1d9acb.png" alt="" /></figure><p>Используя очень простое приложение, так что никаких причудливых шестиугольников, но и там вы получили бы более или менее тот же эффект. Итак, у вас есть некоторый уровень доступа к данным, затем у нас есть некоторый уровень обслуживания, некоторый уровень API, я предполагаю, что обычно используется Spring Boot, и затем, ну, вам нужно обработать некоторые исключения. И что мы видим здесь, в данном конкретном случае, так это то, что мы откровенны, правы, прозрачны, мы передаем эти результаты, потому что хотим быть откровенными и прозрачными, но делаем ли мы что-то с этой прозрачностью?</p><p>Нет. Что вы делаете, так это в контроллерегенерируем исключение, чтобы наш обработчик исключений мог обработать его и вернуть ответ REST. Возможно, это могло бы вернуть результат в нашем контроллере. Да, мы могли бы заставить это работать, но мы ничего с этим не делаем. Итак, что ж, если в 95% случаев вы все равно их не используете, я обнаружил, что, обратившись к Scala, где мы действительно довели это до крайности, это просто работает лучше.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/bfbf4bf7-106b-4830-93de-843a1850dfdd.png" alt="" /></figure><p>И да, это не совсем чистый метод, но он работает лучше. И, конечно, у вас есть остальные 5%. Я думаю, это интересный случай. Итак, мы рассмотрим наши примеры, которые у нас есть.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/390fa31f-0df7-430a-94fc-d686e1ffff43.png" alt="" /></figure><p>Итак, у нас есть наши основные потоки. Затем у нас есть определенные исключения, например, вы не можете получить доступ к своей базе данных. В любом случае, вы не сможете восстановиться после этого. Просто выбросьте его и верните свой ответ об ошибке. Но есть случаи, в основном в API, когда в зависимости от определенной ошибки API мы хотим восстановить ее в нашем сервисе.</p><p>И здесь действительно имеет смысл быть откровенным. Итак, в зависимости от того, что мы получаем , и мы могли бы использовать хороший floor или eternity flow, так что это означает , что для работы с базой данных я бы действительно не стал оборачивать это в монады, просто верните ему сущности, достаточно хороший API сказал, что в зависимости от того, что вы хотите сделать, я бы не стал использовать result кстати, я бы предпочел использовать либо потому что результат в случае, если у вас есть исключения, вам нужно было бы как бы извлечь исключения, чтобы внедрить их в свой бизнес-процесс для принятия других решений, которые в исключительных случаях не принимаются, верно? Поток управления, основанный на исключениях, немного противоречит шаблону. Но с помощью IDER мы можем просто определить тип ошибки, который может быть закрытым интерфейсом с некоторыми классами данных, и тогда мы сможем довольно легко обработать левую часть нашего IDER. Таким образом, поступая таким образом, мы получаем два преимущества.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/39607d9d-cb99-47bc-b3c6-a0fdd2af38a5.png" alt="" /></figure><p>Прежде всего, мы получаем явный контракт. Мы видим, о да, мы можем вернуть именно эти ошибки. И у нас также есть эти операторы для обработки случая, когда мы получаем ошибку, так что мы можем довольно легко восстановить. И если все же произойдет что-то еще, что нас не волнует, выбросьте и позвольте универсальной обработке исключений просто выполнять свою работу. Итак, ключевая мысль здесь заключается в том, что если вы используете монады выборочно, то они действительно великолепны.</p><p>Итак, два примера, прежде чем мы подведем итог. Вот, например, проблема, с которой я тоже сталкиваюсь довольно часто. Чтобы создать какие-то объекты, вы должны проверить их на возможность nullability.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/6f03ded7-d410-4d68-afee-021342a05f94.png" alt="" /></figure><p>Для различных переменных, прежде чем вы передадите их, в противном случае вы не сможете создать такую переменную Kotlin, потому что значения, которые она принимает, не являются null, это довольно утомительно, также здесь у Arrow есть отличная функция, которая заключается в понимании монады с нулевым значением, поэтому в Arrow типы с нулевым значением также считаются монадой, так что вы можете используйте это понимание, а затем вместо использования if-else просто использовать bind, и если одно из этих значений вернет null, то, что ж, весь результат будет null. Итак, я думаю, что это отличное дополнение к вашему коду, позволяющее предотвратить множество конструкций if-else. Еще одна прекрасная вещь — это решение этой конкретной проблемы.</p><figure><img src="https://media.tproger.ru/user-uploads/88534/2024-03-11/51ef84c5-880d-4ecb-a6c6-5dcb38d16e66.png" alt="" /></figure><p>Итак, если у вас глубоко вложенные структуры данных и вы хотите что-то изменить на конечном уровне, и, конечно, мы предполагаем неизменяемые структуры данных, то это довольно утомительная задача — добраться до конечного уровня и преобразовать его, верно? Вы должны копировать, копировать, копировать. На различных конференциях я видел ужасные решения для этого, чрезвычайно сложные с компоновщиками и большим количеством кодирования только для решения этой проблемы. Также здесь Arrow использует так называемую оптику (optics).</p><p>Oxygen также основан на всех этих функциональных концепциях, и они действительно проделывают огромную работу, чтобы решить именно эту конкретную проблему. Таким образом, вы можете просмотреть график с тем, что вы видите в начале, затем вызвать modify, передать конкретный экземпляр, а затем скопировать только тот уровень, который вы хотите скопировать. Это может быть на каждом уровне иерархии вашей структуры данных. Это не то, что вы можете использовать просто так. Для этого вам нужно настроить плагин компилятора, потому что необходимо сгенерировать некоторый код.</p><p>[42:54] Вам также нужно аннотировать свои классы. Но это определенно стоит хлопот, если вы делаете это много раз в своем домене. Это действительно того стоит. Хорошо, итак, я сказал, что как только мы сможем не использовать монады в качестве золотого молотка, бросать его во все подряд, чтобы все наши цепочки вызовов были как бы загрязнены монадами, я думаю, что именно здесь мы достигли пользы функционального здравого смысла. Итак, где вы можете выбрать лучшее и просто пропустить остальное.</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>Функторы и монады: do or do not, there is no try</title>
      <link>https://tproger.ru/articles/do-or-do-not-there-is-no-try</link>
      <comments>https://tproger.ru/articles/do-or-do-not-there-is-no-try?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Sultan]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/do-or-do-not-there-is-no-try</guid>
      <description><![CDATA[<p>Рассказали о функторах и монадах — мощных дополнениях для управления выполнением кода и обработки ошибок в фунциональном программировании.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/do-or-do-not-there-is-no-try">Функторы и монады: do or do not, there is no try</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 18 Dec 2023 13:20:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Основными строительными блоками функционального программирования являются чистые функции. А побочные эффекты являются их злейшими врагами, потому что из-за них функции перестают быть чистыми.</p><p>К примеру, JSON.parse практически обладает всеми признаками чистой функции: она всегда возвращает одинаковый результат при одинаковых входных параметрах и не зависит от глобального контекста. Но всё же в ней есть неприятный сюрприз в виде побочного эффекта.</p><p>Если передать невалидный JSON, то функция не просто не вернет результат, но поломает приложение.</p><p>Впрочем, такое поведение задумано по дизайну, и чтобы обойти эту проблему, нужно просто завернуть вызов функции в try/catch:</p><p>При этом нам пришлось немного увеличить код и использовать мутабельную переменную. Использование let не всегда желательно, так как потребуется более внимательный анализ кода, чтобы отследить все места, где переменная может быть изменена, особенно это усложняется при большем объеме кода.</p><p>Хотелось бы найти более элегантное решение для этой проблемы. Как вариант, можно попробовать добавить метод onError и получить однострочное решение:</p><p>Выглядит красиво, но расширение объектов или функций через прототипы – это игра с огнем, к тому же это не универсальное решение. Каждый раз, когда нам нужно обрабатывать исключение, нам придется сначала изменить исходную функцию, а затем вызывать её. Звучит не очень.</p><p>Но что, если мы создадим функцию-обертку, которая временно расширит любую функцию с методом onError, не изменяя саму функцию?</p><p>Другими словами, функция-обертка будет предоставлять свои методы взаймы, и по завершении выполнения функции, методы будут возвращены обратно владельцу.</p><p>А чтобы функция стала еще удобнее в использовании, можно добавить поддержку композиции функций, примерно так:</p><p>После чего, она стала сильно напоминать Promise, не так ли? Правда, вместо метода onError в Promise используется метод catch. В принципе, можно просто использовать Promise без каких-либо дополнительных функций оберток:</p><p>Такая последовательность функций с использованием then и catch позволяет наглядно управлять потоком и обработкой ошибок. Однако есть небольшая проблема: теперь функция parseName превратилась в асинхронную, значения к переменной будут присвоены не сразу после выполнения функции, а на следующем тике цикла событий (event loop). Это не совсем то, что хотелось бы.</p><p>К счастью, мы можем реализовать функцию, которая будет использовать те же интерфейсы, что и Promise, но будет работать синхронно.</p><p>Ниже приведен пример наглядного использования. Здесь следует отметить, что для прерывания цепочки и вызова исполнения функции необходимо использовать метод finally:</p><p>Интересный факт если вызвать функцию flow с await то она может выполнится без вызова finally, хотя функция синхронная.</p><p>Наша функция выглядит и ведет себя как асинхронная функция, но на практике она не поддерживает асинхронный код. Это может ввести в заблуждение. Чтобы избежать путаницы, предлагаю добавить поддержку асинхронности следующим образом:</p><p>И теперь мы можем комбинировать асинхронные функции в нашем конвейере (pipeline) и не беспокоится где начинается синхронный код, а где асинхронный.</p><h2>Функторы и монады</h2><p>Незаметно для себя, от примера к примеру мы стали применять функторы. Если вы удивлены этим фактом, то спешу сообщить, что функции future, flow, fail являются теми самыми функторами.</p><p>Функторы представляют собой контейнер, который хранит значение. Через функцию, переданную в методы then/catch, мы сможем иметь доступ к значению и изменить его при желании.</p><p>Функтор flow рекурсивно вызывает себя, передавая новое значение, именно поэтому мы можем бесконечно выстраивать цепочки из then/catch, тем самым строить композиции из функций.</p><p>Более того, каждый из then/catch обернуты в try/catch, что позволяет безопасно вызывать функции и контролировать двумя ветками выполнения: успешной веткой (then) и веткой для обработки ошибок (catch). Как только нам понадобится итоговое значение, нужно вызывать функцию finally. После вызова этой функции мы получим результат, и цепочка из then/catch оборвется.</p><p>Примечательно, функция finally может принять функцию, которую можно использовать для финального преобразования.</p><p>Вроде бы всё замечательно, но есть один момент. Если передать в наш поток другой поток, то на выходе мы получим не значение, а функтор.</p><p>Исправить это можно следующим образом:</p><p>Эти изменения делают flow монадой, хотя, конечно, не идеальной, но уже есть основные признаки. Теперь мы сможем использовать вложенные потоки:</p><p>На практике вам вряд ли понадобится использовать монады в чистом виде. Скорее всего, вы будете использовать их внутри функций, которые будут возвращать значения из монады.</p><p>Однако есть случаи, когда использование функторов и монад в методах then/catch может быть оправдано. С помощью функтора fail мы можем прервать выполнение потока без генерации исключения.</p><p>Стоит отметить, что проброс исключения может быть более затратной операцией по сравнению с обычным вызовом функции.</p><p>Другой пример – это функтор halt, который дает возможность прервать выполнение конвейера с определенным результатом.</p><h2>Заключение</h2><p>Является ли данное решение полной заменой для try/catch? Это не замена, а скорее мощное дополнение для управления выполнением кода и обработки ошибок.</p><p>Этот подход удобен в случаях, когда функция может продолжить свое выполнение, несмотря на возникновение ошибки.</p><p>Кроме того, этот шаблон проектирования можно применять не только для обработки ошибок, но и для других целей. Вот некоторые возможные применения:</p><p>В этой статье я намеренно не следовал общепринятым конвенциям и законам монад.</p><p>Моя цель заключалась в том, чтобы познакомить читателей с альтернативным подходом к разработке и раскрыть красоту монады, которую обычно скрывают за академическими терминами и сложными конструкциями.</p><p>Конечно, одной статьей невозможно охватить все аспекты данного подхода, но я надеюсь, что полученная информация станет для вас отправной точкой в мир монад.</p>]]></content:encoded>
    </item>
    <item>
      <title>Самые полезные базовые функции Python</title>
      <link>https://tproger.ru/articles/samye-poleznye-bazovye-funkcii-python</link>
      <comments>https://tproger.ru/articles/samye-poleznye-bazovye-funkcii-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[МТС]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/samye-poleznye-bazovye-funkcii-python</guid>
      <description><![CDATA[<p>Разбираем на примерах, какие встроенные функции Python нужны и в простых вычислениях, и в сложных операциях. Сохраняйте, чтобы не потерять.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/samye-poleznye-bazovye-funkcii-python">Самые полезные базовые функции Python</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 12 Oct 2023 11:13:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Встроенные функции Python — простые и эффективные решения для широкого спектра задач: от простых арифметических вычислений до самых сложных операций над данными.</p><p>Встроенные функции в Python доступны по умолчанию, без использования дополнительных модулей или библиотек. Их можно вызвать в любой момент и в любом месте кода.</p><p>В этой статье подробно рассмотрим ключевые встроенные функции, которые помогают в повседневной работе.</p><h2>Работа с числами</h2><p>Числа — фундаментальная часть любого языка программирования, и Python не исключение. С ними придётся часто сталкиваться, поэтому важно знать и понимать, как работают базовые встроенные функции для числовых данных.</p><ul><li><b>int(): </b>используют для преобразования данных в целочисленный тип. Функция предназначена для представления целых чисел (например, 1, 7, 42).</li></ul><p>Пример:</p><ul><li><b>float(): </b>также используют для преобразования данных в числа с плавающей точкой. Применяется для представления чисел с дробной частью (например, 1.5, 7.89, 42.0).</li></ul><p>Пример:</p><ul><li><b>abs(): </b>возвращает абсолютное значение числа. То есть преобразует отрицательные числа в положительные и оставляет положительные числа без изменений.</li></ul><ul><li><b>min() и max(): </b>функции нужны для поиска минимального и максимального значения из списка или набора чисел соответственно.</li></ul><p>Пример:</p><ul><li><b>sum(): </b>применяют для суммирования элементов списка или объекта.</li></ul><p>Пример:</p><h2>Работа со строками</h2><p>Имена, сообщения, логи или любая другая текстовая информация — мы постоянно сталкиваемся со строками. Базовые встроенные функции делают процесс работы со строками проще и эффективнее.</p><ul><li><b>len():</b> возвращает длину строки или другого итерируемого объекта. Это полезно, когда вы хотите знать количество символов в строке или элементов в коллекции.</li></ul><p>Пример:</p><ul><li><b>str():</b> преобразует различные типы данных в строковый формат. Например, вы хотите комбинировать текст со значениями других типов данных.</li></ul><p>Вот как это будет выглядеть в коде:</p><ul><li><b>input(): </b>используется для чтения строки из стандартного ввода, обычно из клавиатуры. Это полезно для интерактивных программ, где пользователю необходимо ввести какие-то данные.</li></ul><p>Пример:</p><ul><li><b>print(): </b>одна из наиболее часто используемых функций в Python. Выводит информацию на экран (текст, числа и другие типы данных), а также комбинирует их.</li></ul><p>Пример:</p><h2>Работа со списками и коллекциями</h2><p>Коллекции данных — основа большинства программ. Списки товаров, наборы уникальных значений или упорядоченные пары — в Python есть множество удобных инструментов для создания, преобразования и манипулирования коллекциями.</p><ul><li><b>list(), set(), tuple():</b> нужны для создания различных типов коллекций. Функция list() создаёт список, set() — множество (набор уникальных элементов), а tuple() — кортеж (неизменяемый список).</li></ul><p>Пример:</p><ul><li><b>sorted(): </b>сортирует коллекции. Эта функция может сортировать списки, кортежи и даже множества, возвращая отсортированный список.</li></ul><p>Пример:</p><ul><li><b>reversed(): </b>возвращает итератор с элементами в обратном порядке. Чтобы получить структуру данных из итератора, часто её комбинируют с list().</li></ul><p>Пример:</p><ul><li><b>enumerate():</b> нужна для удобного способа получения индексов и соответствующих им значений из списка или другой итерируемой коллекции.</li></ul><p>Пример:</p><h2>Работа с функциональным программированием</h2><p>Python включает в себя несколько встроенных функций для функционального стиля программирования, делая его доступным даже для начинающих разработчиков.</p><ul><li><b>map(): </b>применяет заданную функцию ко всем элементам итерируемого объекта. В результате создаётся новый объект-итератор, который можно преобразовать в список или другую коллекцию.</li></ul><p>Пример:</p><ul><li><b>filter(): </b>фильтрует элементы итерируемого объекта на основе заданной функции.</li></ul><p>Пример:</p><ul><li><b>lambda: </b>позволяет создавать анонимные функции на лету. Это краткая запись функций, которые можно использовать один раз или передать как аргумент другим функциям.</li></ul><p>Пример:</p><h2>Управление выполнением программы</h2><p>При разработке программ часто нужно управлять потоком выполнения: создавать последовательности чисел, определять типы объектов или проверять их принадлежность к определённым классам.</p><ul><li><b>range(): </b>генерирует последовательность чисел. Функция может быть особенно полезна при написании циклов.</li></ul><p>Пример:</p><ul><li><b>type():</b> позволяет определить тип объекта, чтобы узнать, с какими данными вы работаете.</li></ul><p>Пример:</p><ul><li><b>isinstance(): </b>позволяет проверить, принадлежит ли объект к определённому типу или классу (может быть полезно для валидации данных).</li></ul><p>Пример:</p><h2>Работа с ошибками</h2><p>Даже в хорошо написанном коде могут быть ошибки. Python даёт инструменты, которые помогают разработчикам быстро находить и исправлять неполадки, а также учитывать ситуации, которые могут произойти в программе.</p><ul><li><b>try, except: </b>позволяет обрабатывать исключения. Если код в блоке try вызывает ошибку, выполнение переходит к соответствующему блоку except.</li></ul><p>Пример:</p><ul><li><b>raise: </b>позволяет генерировать исключения вручную, что может быть полезно для создания пользовательских условий ошибки.</li></ul><p>Пример:</p><h2>Вывод</h2><p><b>✔</b> Базовые встроенные функции покрывают множество задач, которые регулярно возникают при программировании на Python.</p><p><b>✔ </b>Хорошее знание базовых встроенных функций ускоряет рабочий процесс и делает код более чистым и эффективным.</p><p><b>✔ </b>Существуют и другие базовые встроенные функции, которые мы не разобрали в статье, но они реже используются на практике.</p>]]></content:encoded>
    </item>
    <item>
      <title>Декларативный JavaScript</title>
      <link>https://tproger.ru/articles/deklarativnyj-javascript</link>
      <comments>https://tproger.ru/articles/deklarativnyj-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Sultan]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/deklarativnyj-javascript</guid>
      <description><![CDATA[<p>На примере JavaScript объяснили, как пересекается декларативное программирование с функциональным, и как кодить на JS декларативно.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/deklarativnyj-javascript">Декларативный JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 10 Oct 2023 12:01:59 GMT</pubDate>
      <content:encoded><![CDATA[<p>Эта статья ориентирована на читателей, которые уже знакомы с функциональным программированием, но, возможно, не до конца разобрались с применением некоторых ключевых идей на практике. Мы постараемся разобрать их на конкретных примерах и понять, как пересекается декларативное программирование с функциональным. Следует отметить, что в контексте JavaScript применение функционального и, тем более, декларативного программирования в их чистой форме невозможно. Таким образом, далее речь пойдет о принципах и подходах, характерных для функционального стиля программирования.</p><p>В статьях, посвященных декларативному программированию, зачастую упоминаются SQL, HTML в качестве примеров декларативных языков. В рамках этих “языков” мы действительно формулируем желаемый результат, вместо инструкции по его достижению, что вполне соответствует рассматриваемому концепту. Однако я хочу выделить общую черту этих <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA">доменных языков</a>:</p><p>В декларативных языках отсутствуют переменные, циклы и логические конструкции.</p><p>Писать код на JavaScript с таким тезисом звучит как вызов и в какой-то степени абсурдно, не правда ли?</p><p>Вспомните, как часто вам приходилось писать подобный код в JSX, чтобы показать или скрыть компонент:</p><p>Давайте перепишем этот кусочек кода в <i>более</i> декларативной манере:</p><p>Как было сказано, это <i>более </i>декларативный вариант кода. В конечном итоге мы все равно упремся на логическую конструкцию `if`, но мы намерены прятать его все дальше от основного кода.</p><p>Наличие свойства `visible` будет подталкивать нас к написанию декларативного кода несмотря на то, что сам компонент частично реализован в императивном стиле.</p><p>Попробуем разобрать логический оператор `switch`, который является не только ближайшим родственником `if`, но и любимчиком многих разработчиков:</p><p>Наверное вы уже догадываетесь, к чему я веду, особенно учитывая, что подобный подход уже реализован в React Router.</p><p>Но я хочу рассмотреть данный код с позиции функционального программирования, а именно, представить теги HTML/JSX в виде функций, которые возвращают HTML, где атрибуты тега являются входными параметрами функции.</p><p>Основываясь на вышеупомянутую идею, перепишем JSX-код в виде наборов функций. Стоит учесть, что `switch/case` — это зарезервированные слова JavaScript, поэтому мы добавим к ним нижние подчеркивания:</p><p>Как вы можете заметить, декларативный код не обязательно означает HTML, JSX или SQL. В JavaScript мы можем представить синтаксис этих языков с помощью функций. Если нас учили, что функции должны представлять собой действия и их названия должны начинаться с глагола, например: `find`, `setTitle`, то в данном случае наши функции не всегда будут выражать действие и могут обозначать сущность. К примеру, SQL-запрос можно записать в следующем виде:</p><p>Таким образом я хочу протянуть нить между декларативным и функциональным программированием.</p><p>Я не считаю, что функциональное программирование преследует декларативный подход, скорее это побочный эффект от интенсивного использования функций.</p><p>Важно отметить, что функциональное программирование это не просто <a href="https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself">DRY </a>принцип, где повторяющийся код выносится в отдельную функцию. Это техника написания универсальных функций, которые должны легко комбинироваться и предоставлять гибкость в использовании.</p><p>Предлагаю еще раз взглянуть на конструкции `switch/case` и представить их в виде обычной функции:</p><p>Данная реализация не обладает гибкостью и не может быть использована в композиции с другими функциями. Тем не менее, преобразовав функцию `selectComponent` в функцию высшего порядка и дополнив её вспомогательными функциями, можно достичь гибкости и универсальности.</p><p>Если вы не до конца понимали для чего нужны каррированные функции, то это хорошая иллюстрация практического применения. Каррированная функция представляет собой функцию, аналогичную обычной, но с дополнительной особенностью, которая позволяет приостанавливать её выполнение. То есть, мы вызываем её, передавая один параметр, и выполнение откладывается до момента, когда будут переданы все необходимые параметры. Такой подход позволяет описывать действия до фактического выполнения самой функции, что позволяет писать код в декларативном стиле.</p><p>Для ясности, предлагаю развернуть функцию `selectComponent`, после вызова функций `when` мы получим следующий код:</p><p>Гибкость проявляется в том, что мы сможем создать множество других версий `when`, но главная функция `select` не потребует изменений. Ниже пример функции с поддержкой ленивой загрузки компонентов:</p><p>Если мы вернемся к первой версии функции `selectComponent`, станет ясно, что расширить её функционал без внесения изменений не получится. Используя данный подход, мы способствуем разбиению кода на мелкие функции, каждая из которых решает конкретную задачу, и в дальнейшем они менее подвержены изменениям.</p><p>Когда требуется расширить функционал, мы просто добавляем новую функцию и комбинируем её с уже существующими, вместо того, чтобы каждый раз переписывать основную функцию.</p><p>В следующей статье мы подробно рассмотрим данную технику на примере создания редьюсеров Redux. Узнаем, как заменить `try/catch`  и продолжим обсуждение функционального программирования.</p>]]></content:encoded>
    </item>
    <item>
      <title>Современные языки программирования, которые заставят вас страдать: Часть 2, функциональные языки</title>
      <link>https://tproger.ru/translations/sovremennye-jazyki-programmirovanija-kotorye-zastavjat-vas-stradat-chast-2-funkcionalnye-jazyki</link>
      <comments>https://tproger.ru/translations/sovremennye-jazyki-programmirovanija-kotorye-zastavjat-vas-stradat-chast-2-funkcionalnye-jazyki?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Олег Борисенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/sovremennye-jazyki-programmirovanija-kotorye-zastavjat-vas-stradat-chast-2-funkcionalnye-jazyki</guid>
      <description><![CDATA[<p>Это продолжение перевода рейтинга современных языков программирования. В этот раз речь пойдёт о функциональных языках программирования.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/sovremennye-jazyki-programmirovanija-kotorye-zastavjat-vas-stradat-chast-2-funkcionalnye-jazyki">Современные языки программирования, которые заставят вас страдать: Часть 2, функциональные языки</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 19 Jan 2021 11:53:46 GMT</pubDate>
      <content:encoded><![CDATA[<p>Прим. ред. Это перевод статьи Ильи Суздальницкого. Мнение редакции может не совпадать с мнением автора оригинала.</p><p>Это вторая и финальная часть перевода статьи про современные язки программирования. <a href="https://tproger.ru/translations/sovremennye-jazyki-programmirovanija-kotorye-zastavjat-vas-stradat-chast-1-oop">В первой части</a> рассматривались объектноориентированные языки. В этой части автор подробно разбирает функциональные языки программирования которые принадлежат семейству ML ( и некоторые С-подобные).</p><h2>Функциональные языки программирования</h2><h3>Haskell</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/haskell-280x258.png" alt="" /></figure><p>Сложность изучения: чтобы продуктивно использовать Haskell нужно изучить теорию категорий. Даже для того, чтобы написать hello world, нужно понимать монады.</p><p>Сообщество: больше заинтересовано в академических дискуссиях, чем в решении реальных проблем.</p><p>Функциональная чистота: чистые функции — прекрасны. Побочные эффекты (взаимодействие с внешним миром, изменение состояния) причина большого количества ошибок в программах. Будучи чисто функциональным языком, Haskell полностью избавлен от них.</p><p>Конечно в языке есть обходные пути, для взаимодействия с внешним миром. Для этого используется набор инструкций (монад ввода-вывода). Это могут быть такие инструкции: получить строку с клавиатуры, использовать её в некой функции, напечатать результат в консоль. Среда выполнения делает это за нас. Мы никогда не выполняем код взаимодействующий с внешним миром напрямую.</p><p>На практике, такая сосредоточенность на функциональной чистоте значительно увеличивает число абстракций, усложняет код и уменьшает продуктивность разработчиков.</p><p>Поддержка NULL: как и в Rust, Haskell не поддерживает нулевые ссылки. Вместо этого в нём есть Optional, на случай, если значения может не быть.</p><p>Обработка ошибок: некоторые функции могут выбрасывать ошибки, но более свойственный для языка подход похож на Result в Rust.</p><p>Иммутабельность: язык имеет первоклассную поддержку иммутабельных структур данных.</p><p>Сопоставление с образцом: поддерживается.</p><p>Экосистема: стандартная библиотека неорганизованна. По умолчанию, в Haskell используются функции выбрасывающие исключение, вместо возврата Option (золотой стандарт для функционального программирования). В довершение всего, у Haskell есть два менеджера пакетов — Cabal и Stack.</p><p>Вердикт: мне бы очень хотелось полюбить Haskell, однако он навсегда застрял в академических кругах. Является ли он худшим из функциональных языков? Решать вам, но я думаю, что это так.</p><h3>OCaml</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/ocaml.png" alt="" /></figure><p>Типизация: не поддерживает классы типов, но есть функторы (модули высшего порядка). Язык статически типизирован и выводит типы почти также хорошо, как Haskell.</p><p>Экосистема: имеет небольшое сообщество и страдает от недостатка библиотек. Языку не хватает достойного веб-фреймворка. Документация хуже чем других языков.</p><p>Инструментарий: инструменты языка неорганизованны. Имеет три менеджера пакетов: Opam, Dune, и Esy. Язык известен некачественными сообщениями об ошибках компилятора. Это не критично, но снижает производительность программистов.</p><p>Обучающие ресурсы: главная книга для изучения OCaml — <a href="https://www.amazon.com/Real-World-OCaml-Functional-programming/dp/144932391X">«Real World OCaml»</a>, не обновлялась с 2013 года. Примеры из книги устарели. Туториалов по языку недостаточно, а большая часть из них это конспекты лекций.</p><p>Параллелизм: разработчики годами ждут поддержки многоядерности, но её пока что не предвидится.</p><p>Поддержка NULL: нет нулевых ссылок, использует Option для неуказанных значений.</p><p>Обработка ошибок: нативный подход — использование типа Result.</p><p>Иммутабельность: язык имеет первоклассную поддержку иммутабельных структур данных.</p><p>Сопоставление с образцом: поддерживается.</p><p>Вердикт: Ocaml — хороший функциональный язык. Его основные недостатки: отсутствие поддержки многоядерности и небольшое сообщество (причина недостатка обучающих материалов и библиотек). Поэтому, я бы не рекомендовал язык к использованию в работе.</p><h3>Scala</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/scala.png" alt="" /></figure><p>Экосистема: Scala — это язык из семейства Си, который выполняется на виртуальной машине Java. Это значит, что у вас есть доступ к огромной экосистеме библиотек Java.</p><p>Типизация: язык плохо справляется с приведением типов. Однако Scala поддерживает Higher-Kinded типы и типы классов.</p><p>Немногословность/читаемость: хотя программы на Scala и отличаются лаконичностью (особенно по сравнению с Java), читаемость страдает. Scala — один из немногих функциональных языков, принадлежащих к семейству Си. Си-подобные языки были предназначены для императивного программирования, а ML для функционального. Поэтому функциональный код на Scala может иногда выглядеть странно.</p><p>Синтаксис для алгебраических типов данных оставляет желать лучшего:</p><p>Этот же код на языке ReasonML:</p><p>Скорость: hello world на языке Scala может компилироваться до 10 секунд, на слабом железе. Компиляция производится только на одном ядре процессора, что отрицательно влияет на скорость.</p><p>Из-за того, что Scala работает на виртуальной машине Java, программы запускаются дольше.</p><p>Сложность изучения: один из самых сложных функциональных языков. Scala, как и C++ обладает множеством функций, которые, однако, усложняют его изучение.</p><p>Иммутабельность: Scala обладает первоклассной поддержкой неизменяемых структур данных (с использованием классов образцов).</p><p>Поддержка NULL: с одной стороны, Scala поддерживает нулевые ссылки. С другой стороны, характерный для языка способ обработки отсутствующих значений — паттерн Option.</p><p>Обработка ошибок: нативный подход — использование типа Result.</p><p>Параллелизм: можно использовать отличный инструмент — <a href="https://en.wikipedia.org/wiki/Akka_(toolkit)">Akka.</a></p><p>Сопоставление с образцом: поддерживается.</p><p>Вердикт: Scala пытается делать слишком многое. Его разработчикам пришлось пойти на множество компромиссов, чтобы поддерживать как ООП, так и функциональное программирование.</p><h3>Elm</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/elm.png" alt="" /></figure><p>Обработка ошибок: в языке нет ошибок выполнения и исключений. Как и другие функциональные языки, использует тип Result для обработки ошибок.</p><p>Функциональная чистота: как и Haskell, Elm — чисто функциональный язык. И в данном случае это скорее минус, потому что любой рефакторинг превращается в кошмар.</p><p>Слишком строгий:</p><figure><img src="https://media.tproger.ru/uploads/2021/01/ElmError.png" alt="" /><figcaption>Скриншот с reddit.com<br /></figcaption></figure><p>Elm настолько строгий, что использование табуляций считается синтаксической ошибкой.</p><p>Сосредоточенность на отсутствии ошибок убивает язык. В версии 0.19, взаимодействие с JS библиотеками сделали практически невозможным. Конечно для того, чтобы стимулировать людей писать свои библиотеки на Elm. Но компаний, у которых есть для этого достаточно ресурсов, крайне мало.</p><p>Поддержка React: Elm создаёт свою собственную виртуальную модель DOM и не использует React. Это лишает разработчиков доступа к обширной экосистеме библиотек и компонентов, созданных для React.</p><p>Состояние языка: с каждым новым релизом в языке происходят сильные изменения, которые могут лишить вас возможности использовать его.</p><p>К сожалению, прошло уже больше года с тех пор, как была выпущена новая версия Elm (0.19.1). О состоянии разработки ничего не известно. Возможно, что она вообще больше не ведётся.</p><p>Сопоставление с образцом: поддерживается.</p><p>Иммутабельность: обладает первоклассной поддержкой неизменяемых структур данных.</p><p>Поддержка NULL: нет нулевых ссылок, использует Option для неуказанных значений.</p><p>Вердикт: Elm — отличный язык, но к сожалению у него нет будущего.</p><h3>F#</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/f.png" alt="" /></figure><p>Не полностью функциональный: в отличие от Haskell/Elm, F# очень прагматичен и не обеспечивает функциональную чистоту.</p><p>Обучающие ресурсы: есть действительно хорошие учебные <a href="https://fsharpforfunandprofit.com/">ресурсы</a>.</p><p>Сложность изучения: F# — один из самых простых функциональных языков.</p><p>Экосистема: имеет довольно небольшое сообщество и в отличие от таких языков как Elixir, оно не имеет таких же замечательных библиотек.</p><p>Взаимодействие с C#: F# имеет доступ ко всей экосистеме .NET/C#. Действительно хорошо взаимодействует с существующим C# кодом.</p><p>Параллелизм: работает поверх <a href="https://ru.wikipedia.org/wiki/Common_Intermediate_Language">CLR</a>, который не имеет такой же превосходной поддержки параллелизма, как Elixir на виртуальной машине Erlang.</p><p>Поддержка NULL: NULL-значения обычно не используются. Неуказанные значения обрабатываются с помощью паттерна Option.</p><p>Обработка ошибок: ошибки обрабатываются с помощью паттерна Result.</p><p>Иммутабельность: обладает первоклассной поддержкой неизменяемых структур данных.</p><p>Сопоставление с образцом: поддерживается.</p><p>Вердикт: F# — очень надежный язык программирования с действительно хорошей системой типов. Он почти так же хорош, как Elixir для разработки Web API (подробнее об этом далее). Однако проблема F# заключается не в том, что у него есть, а в том, чего у него нет. Если сравнить его с Elixir, его функционал параллелизма, богатая экосистема и удивительное сообщество перевешивают любые преимущества статической типизации, которые предоставляет F#.</p><p>Однако F# — лучший язык для финтеха. Также язык отлично подойдёт для энтерпрайз разработки. Его мощная система типов позволяет моделировать сложную бизнес логику. Очень рекомендую прочитать эту книгу — «<a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/">Domain Modeling Made Functional»</a>.</p><h3>ReasonML</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/reasonML.png" alt="" /></figure><p>Не является надмножеством JavaScript: синтаксис ReasonML похож на JavaScript, что делает его более доступным для всех, кто имеет опыт работы с JavaScript. Однако, в отличие от TypeScript, ReasonML даже не пытается быть надмножеством JavaScript. ReasonML не должен был унаследовать плохие дизайнерские решения, десятилетиями принимаемые в JavaScript.</p><p>Сложность изучения: ReasonML является одним из самых простых функциональных языков.</p><p>Не полностью функциональный: ReasonML очень прагматичен, ориентирован на производительность разработчиков и быстрое достижение результатов.</p><p>Типизация: его система типов почти так же хороша, как у Haskell. Самым большим недостатком является отсутствие классов типов, но он поддерживает функторы (модули высшего порядка).</p><p>ReasonML статически типизирован и выводит типы почти так же хорошо, как и Haskell.</p><p>Экосистема: как и TypeScript, ReasonML имеет доступ ко всей экосистеме JavaScript.</p><p>Взаимодействие с JavaScript/TypeScript: компилируется в обычный JavaScript. Поэтому, в одном проекте можно использовать как ReasonML, так и JavaScript/TypeScript.</p><p>ReasonML and React — отличное сочетание: поскольку ReasonML статически типизирован, нет необходимости беспокоиться о PropTypes. В отличие от JavaScript, при использовании ReasonML ничто не перерисовывается без необходимости — вы получаете отличную производительность React из коробки!</p><p>Инструменты: язык далеко не такой зрелый, как его альтернативы, так что могут возникнуть некоторые проблемы с инструментами. Например, официально рекомендуемое расширение VSCode — <a href="https://github.com/jaredly/reason-language-server">reason-language-server</a> в настоящее время не работает.</p><p>ReasonML использует компилятор OCaml под капотом, а OCaml известен посредственными сообщениями об ошибках компилятора. Это не критично, но может повлиять на производительность разработчиков.</p><p>Поддержка NULL: нет нулевых ссылок, использует Option для неуказанных значений.</p><p>Иммутабельность: обладает первоклассной поддержкой неизменяемых структур данных.</p><p>Сопоставление с образцом: поддерживается.</p><p>Вердикт: это отличный язык для веб-разработки. ReasonML, вероятно, является тем, чем всегда стремился быть TypeScript, но потерпел неудачу. ReasonML добавляет статическую типизацию в JavaScript, убирая при этом все плохие фичи (и добавляя современные фичи, которые действительно нужны).</p><h3>Elixir</h3><figure><img src="https://media.tproger.ru/uploads/2021/01/elixir.png" alt="" /></figure><p>Имеет хорошую документацию, даже к стандартной библиотеке.</p><p>Фреймворк Phoenix: поддерживает из коробки: вебсокеты, routing, HTML templating language, internationalization, JSON encoders/decoders, seamless ORM integration(Ecto), sessions, SPA toolkit и многое другое. Также фреймворк известен своей производительностью — способен обрабатывать <a href="https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections">миллионы одновременных подключений</a> на одной машине.</p><p>Фуллстек Elixir: Phoenix недавно представил <a href="https://elixirschool.com/blog/phoenix-live-view/">LiveView</a>, который позволяет создавать насыщенные веб-интерфейсы реального времени прямо в Elixir. LiveView даже заботится о синхронизации состояния клиента и сервера, а это значит, что нам не нужно беспокоиться о разработке и обслуживании REST/GraphQL API.</p><p>Обработка данных: Elixir может быть надежной альтернативой Python для многих задач связанных с обработкой данных. Скрепер есть как у Python, так и у Elixir, и последний предлагает гораздо лучшее решение и экосистему для этой задачи.</p><p>Такие инструменты, как <a href="https://github.com/dashbitco/broadway">Broadway</a>, позволяют строить конвейеры приема/обработки данных в Elixir.</p><p>Типизация: на мой взгляд, отсутствие корректной статической типизации — самый большой недостаток Elixir.</p><p>Скорость: компилятор Elixir является многопоточным и обеспечивает невероятно высокую скорость компиляции. В отличие от JVM, виртуальная машина Erlang запускается быстро. Производительность во время выполнения очень хороша.</p><p>Надёжность: код на Elixir выполняется поверх Erlang, который использовался более 30 лет для создания самого надежного программного обеспечения в мире. Некоторые программы, работающие на виртуальной машине Erlang, смогли достичь надежности <a href="https://stackoverflow.com/questions/8426897/erlangs-99-9999999-nine-nines-reliability">99,9999999%</a>. Ни одна другая платформа в мире не может похвастаться таким же уровнем надежности.</p><p>Параллелизм: язык использует альтернативный подход к параллелизму — <a href="https://www.brianstorti.com/the-actor-model/">модель акторов</a>. При таком подходе, у акторов (процессов) нет ничего общего. Единственный способ общения между различными процессами — отправка сообщений.</p><p>Elixir, в отличие от Go, убивает только тот процесс в котором произошла ошибка, а не всю программу. Более того, этот процесс будет автоматически перезапущен его супервизором.</p><p>Elixir строится на основе Erlang, который известен своими большими возможностями распараллеливания, и использует совершенно другой подход к параллелизму, называемый моделью актора. В рамках этой модели, между процессами (акторами) нет ничего общего. Каждый процесс поддерживает свое собственное внутреннее состояние, и единственный способ общения между различными процессами — отправка сообщений.</p><p>Процессы в Elixir очень легковесны, их можно запускать тысячами на одной машине.</p><p>Масштабирование: параллельные вычисления в Go быстрее чем в Elixir, если это происходит на одной машине. Но при масштабировании происходит обратное. Elixir легко справляется с такими вещами как: кластеризация, RPC и сетевые взаимодействия. В некотором смысле, виртуальная машина Erlang работала с микросервисами за десятилетия до того, как они вошли в обиход. Каждый процесс можно рассматривать как микросервис — как и микросервисы, процессы независимы друг от друга. Микросервисы без сложностей Kubernetes? Именно для этого и был создан Elixir.</p><p>Обработка ошибок: язык использует уникальный подход к обработке ошибок. В то время как чисто функциональные языки (Haskell/Elm) предназначены для минимизации вероятности появления ошибок, Elixir предполагает, что ошибки неизбежно произойдут.</p><p>Выбрасывать исключения в Elixir — правильно, в то время как обрабатывать исключения обычно не рекомендуется. Вместо этого супервизор процесса автоматически перезапустит неудачный процесс, чтобы программа продолжила работать.</p><p>Сложность изучения: язык можно <a href="https://pragprog.com/titles/elixir16/programming-elixir-1-6/">освоить</a> за пару месяцев. Однако освоение <a href="https://pragprog.com/titles/jgotp/designing-elixir-systems-with-otp/">OTP</a> может занять некоторое время. OTP — киллер фича языка. OTP – это набор инструментов и библиотек от Erlang, на которых строится Elixir. Это секретный ингредиент, который значительно упрощает построение параллельных и распределенных программ.</p><p>Обучающие ресурсы: их существует огромное количество. И почти все из них подойдут для новичков.</p><p>Сопоставление с образцом: поддерживается.</p><p>Вычисления: язык не справляется с задачами в которых требуется большое количество вычислений.</p><p>Вердикт: Elixir, вероятно, является самым зрелым из всех функциональных языков. Он работает на виртуальной машине, созданной для функционального программирования. Язык был разработан с нуля для параллельных вычислений, и идеально подходит для современной эры многоядерных процессоров. Это лучший язык для Web API. OTP и модель акторов делают язык лучшим решением для параллельных и распределённых программ.</p><h2>Подходящий инструмент</h2><p>Точно также, как не стоит пытаться забить гвоздь отвёрткой, не нужно использовать один язык программирования повсюду. Каждый из них имеет свою область применения.</p><p>Go — лучший язык для системного программирования. Для фронтенда несомненно стоит выбрать ReasonML. Абсолютный лидер для разработки Web API — Elixir. Как и для любых задач связанных с параллельными и распределёнными программами. Python, это к сожалению единственный адекватный вариант для data science.</p><p>Это довольно неоднозначная статья. Очевидно, что автор предпочитает функциональные языки программирования объектноориентированным. Если вы не согласны с рейтингом и можете аргументировать свою точку зрения, добро пожаловать в комментарии.</p>]]></content:encoded>
    </item>
    <item>
      <title>Когда применять функциональное программирование, а когда ООП — отвечают эксперты</title>
      <link>https://tproger.ru/experts/oop-vs-functional-programming</link>
      <comments>https://tproger.ru/experts/oop-vs-functional-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/oop-vs-functional-programming</guid>
      <description><![CDATA[<p>Эксперты объясняют, в каких задачах уместна каждая парадигма и почему опытные разработчики выбирают её по эффективности, а не по симпатии.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/oop-vs-functional-programming">Когда применять функциональное программирование, а когда ООП — отвечают эксперты</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 03 Feb 2020 09:07:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Многие слышали про функциональное программирование и, возможно, задавались вопросом: «А зачем оно, когда есть ООП?». Мы спросили у экспертов, когда стоит использовать ту или иную парадигму.</p><p>Каждый инструмент хорош для определённого набора задач в определённой ситуации. Как некоторые могут забивать саморезы молотком, так и фанаты той или иной парадигмы при любых вводных могут задействовать то, чем лучше владеют. Однако опытные разработчики всё-таки руководствуются не симпатией к какой-либо технологии, а эффективностью. Также могут присутствовать ограничения, задаваемые заказчиком или программным окружением. Важен целый ряд факторов и характеристик разрабатываемого решения, его дальнейшей поддержки. Разумеется, выбор чаще опирается на собственный опыт, и в самом начале проектирования не всегда удаётся угадать будущее развитие и все нюансы использования ПО, так что универсального решения нет.</p><p>Объектно-ориентированное программирование (ООП) является более «традиционной» парадигмой. С её помощью разработано несчётное количество программ, в том числе огромные промышленные системы в финансовых организациях, телекоммуникации на производстве, складах, транспорте. Незнание принципов ООП фактически перекроет доступ ко всем этим системам.</p><p>Тут надо понимать, что если десяток лет пользовался ООП, то и сознание подстраивается под эту модель, проще проектировать именно через объекты и вызовы методов, а не через потоки данных и данные. Один из главных минусов ООП — чудовищная, запутанная система классов для системы, которая разрабатывается десятки лет большой командой. Как правило, некоторые «ядерные» классы оказываются на «дне» модели, их никто не рискует трогать даже в ущерб скорости разработки и устойчивости ПО. Появляются классы-наследники, переопределённые методы и прочий мусор, который со временем тоже становится «ядром системы».</p><p>Что касается функционального программирования (ФП). В каком-то сильно упрощённом виде оно используется и при ООП — в школах первые программы пишут функциями. С него нужно начинать изучение языков программирования, им же и завершать. Далее, в зависимости от нужд конкретного проекта, можно углублять знания той парадигмы, которая больше используется. Парадигма ФП влияет и на программирование, и на проектирование программного обеспечения. Для высоконагруженных систем переход к обработке потоков данных может быть спасением. Для выбора этой парадигмы в «большой» системе как минимум нужно иметь много данных и большую нагрузку (много вызовов, много пользователей). С одной стороны, она может дистанцировать бизнес-модель от реализации, с другой — позволит вовремя отвечать на запросы пользователей и иных внешних систем. Для небольших программ выбор ФП возможен, тут больше дело вкуса. Однако новичку может быть непросто разделить бизнес-модель на данные и потоки данных и спроектировать так, чтобы данные не хранились в классах и было чистое ФП.</p><h2>Что выбрать новичку для изучения?</h2><p>Изучить всё сразу не получится, но для быстрого старта карьеры, на мой взгляд, достаточно знать принципы ООП и иметь хотя бы общее представление о функциональных, процедурных языках: современные подходы используют некоторые более старые парадигмы, в новой реализации они могут быть очень эффективны. Если есть поверхностное знание о функциональном программировании — это вообще замечательно. Значит, у разработчика есть выбор, меньше ограничений на реализацию задуманного.</p><p>Я считаю, что каждый разработчик должен иметь представление и об ООП, и о ФП, знать сильные и слабые стороны каждого подхода и на основе этого определять, что лучше использовать для решения конкретной бизнес-задачи.</p><p>ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных (например содержат внутри себя ссылки на глобальные переменные) или коммуницируют с внешним миром (ввод-вывод).</p><p>В отличие от ООП, функциональное программирование характеризуется слабой связью функции с данными, которыми она оперирует. Это позволяет избежать побочных эффектов при выполнении функций — например чтения и изменения глобальных переменных, операций ввода-вывода и так далее. Детерминированные функции ФП возвращают один и тот же результат для одних и тех же аргументов.</p><p>Но эти подходы не являются взаимоисключающими. Нет необходимости выбирать только одну парадигму и следовать ей до конца. Вы можете передавать классы в чистые (то есть не связанные с внешними данными) функции или можете использовать чистые функции в качестве методов класса — одно другому не противоречит, а только дополняет. Если вы пишете простую и небольшую программу, следование той или иной парадигме — сугубо ваше личное мнение и видение прекрасного. Однако если вы пишете большой сервис с разноплановыми задачами, в определённый момент вы столкнетесь с необходимостью рефакторинга, так как для эффективного решения всех этих задач одного подхода будет недостаточно.</p><p>Приведу пример. Если вы пишете на Node.js, на первый взгляд удобнее использовать ФП. Дело в том, что сам по себе запрос на сервер — это функция с определённым входом и выходом (request, response). А работа с request’ом происходит с помощью цепочки функций (middleware), и результат (response) всегда будет одинаковый, если в качестве аргументов передавать одни и те же значения. Функциональный подход здесь смотрится естественно. С другой стороны, если Node.js-сервис подразумевает работу с БД, для описания моделей и работы с ними удобнее применить ООП-подход. Примером служит популярная библиотека sequelize.</p><p>На просторах frontend особой популярностью пользуются фреймворки, и каждый из них использует ту или иную парадигму, но для полноценной работы с ними необходимо знание как ООП, так и ФП. Взять для примера Angular: данный фреймворк построен на сервисах, которые в свою очередь являются классами, содержащими данные и методы для работы с ними. Однако при работе с библиотекой Redux, обычно работающей в паре с React, напрямую сталкиваешься с функциональным подходом, так как основная идея Redux — использование чистых функций без побочных эффектов.</p><p>ООП vs. ФП — вечная дилемма. Мне кажется, что наибольшей эффективности в разработке можно добиться, только если миксовать подходы. Точно не стоит писать проект только на ФП, потому что такой подход сильно ограничивает разработку: нужно постоянно прорабатывать поведение state.</p><p>Я бы порекомендовал начинающим разработчикам посмотреть отдельные, частные кейсы. Например, хороший кейс использования ФП, когда в проекте есть некая сортировка данных. Условно, есть датасет, в котором нужно отфильтровать данные по фамилии, а затем ещё и по имени. Функциональное программирование позволяет сделать это красиво и с умом.</p><p>Всегда стоит думать о логике проекта. Если в приложении много динамики, то архитектура REST API из функционального программирования отлично сработает, так делают те же ребята из tutu.ru. То же самое работает и наоборот — если у вас обычное сервисное приложение, где пользователю отображают JSON, то особо смысла использовать ФП нет.</p><p>«Когда применять функциональное программирование, а когда ООП?» — вопрос совсем непростой. Если посмотреть форумы, то понятно, что холивар возникает уже на этапе самого определения функционального программирования.</p><p>Если исходить из определения, что функциональный стиль — это когда результат выполнения кода всегда зависит только от поданных на вход значений, то лично я такой подход стараюсь применять как можно чаще. Это упрощает читабельность кода, его тестирование. Однако ФП более характеризуется тем, что аргументами одних функций являются другие, более простые функции, и вот это наиболее сложная часть, где можно легко выскочить за сложность вычислений O(n).</p><p>Не очень понятно, как сравнить ООП и функциональное программирование (ФП). Я считаю, что модели могут тесно пересекаться, не исключая друг друга, и где какую модель применять, зависит от архитектуры программы и задач, стоящими перед каждым модулем программы. Например ваш код имитирует движение транспортного средства (ТС). Нужно каждую секунду вычислять координаты ТС, его скорость, пройденный путь. Для того, чтобы это сделать, необходимо будет производить вычисления на основе предыдущих вычислений. Если применить функциональное программирование, то появляется необходимость хранения результатов вычислений и подачи их каждый раз на вход модели. Это создаст дополнительные логические сложности, поэтому в этой задаче лучше скомбинировать ООП и ФП. Для каждого ТС создаётся объект в стиле ООП, и каждый объект сам хранит свои предыдущие вычисления. Вам остаётся на вход подавать только ускорение, направление движения и время, в течение которого они действовали. Внутри же объекта ТС у вас будет два десятка методов, рассчитывающих его новое состояние. И вот тут рекомендация: стремиться большую часть из них сделать в виде простых функций, и только в одном или двух в вычисления добавить влияние его предыдущего состояния или используемого набора функций.</p><p>Сейчас порог входа в программисты очень высок, и изучать что-то одно и быть востребованным у программиста вряд ли получится.</p><h2>Итак, какую парадигму выбрать?</h2><p>ООП-подход подразумевает написание базовых классов и расширение существующих путем добавления к ним методов. Данные хранятся в экземпляре класса вместе с методами, которые ими оперируют. Функции в ООП зависят от внешних данных.</p><p>Функциональное программирование характеризуется слабой связью функции с данными, которыми она оперирует. Это позволяет избежать побочных эффектов при выполнении функций — например чтения и изменения глобальных переменных, операций ввода-вывода и так далее.</p><p>Тем не менее, эти подходы не являются взаимоисключающими. Вы можете передавать классы в чистые функции или использовать чистые функции в качестве методов класса. Нередко удачным подходом является именно смешение парадигм, а не использование какой-то одной.</p><p>При выборе парадигмы стоит смотреть на решаемую вами задачу, а также учитывать возможное развитие проекта, чтобы быть уверенным, что выбранная сегодня «правильная» парадигма не вынудит вас через полгода переписать весь проект.</p><p>Что до новичков, то определённо стоит изучить ООП, так как это более «традиционная» парадигма, которая много где используется. Но стоит иметь хотя бы общее представление о ФП, чтобы иметь у себя в арсенале ещё один инструмент.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Мнение: объектно-ориентированное программирование — катастрофа на триллион долларов</title>
      <link>https://tproger.ru/translations/oop-the-trillion-dollar-disaster</link>
      <comments>https://tproger.ru/translations/oop-the-trillion-dollar-disaster?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/oop-the-trillion-dollar-disaster</guid>
      <description><![CDATA[<p>Критика объектно-ориентированного программирования: автор называет его катастрофой на триллион долларов и сравнивает с функциональным подходом.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/oop-the-trillion-dollar-disaster">Мнение: объектно-ориентированное программирование — катастрофа на триллион долларов</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 04 Sep 2019 13:08:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Илья Суздальницкий, senior full-stack-разработчик</p><p>Прим. ред. Мнение редакции может не совпадать с мнением автора оригинала.</p><p>По мнению многих, ООП является жемчужиной информатики. Идеальное решение для организации кода. Конец всем проблемам. Единственный верный способ написания программ. Дарован нам самим истинным Богом программирования.</p><p>Но это не так. Люди начинают уступать под тяжестью абстракций и сложного графа беспорядочно разделяемых изменяемых объектов. Драгоценное время и силы тратятся на размышления об абстракциях и шаблонах проектирования вместо решения реальных проблем. Многие критикуют объектно-ориентированное программирование, в том числе очень выдающиеся разработчики программного обеспечения. Даже сам изобретатель этой парадигмы известен своей критикой современного ООП.</p><p>Цель каждого разработчика — написать надёжное программное обеспечение. Ничто другое не имеет значения, если код глючит. При этом самый лучший подход к написанию надёжного кода — простота. Следовательно, первая и главная цель разработчиков должна заключаться в уменьшении сложности кода.</p><h2>Дисклеймер</h2><p>Буду честен, я не ярый поклонник ООП. И конечно, эта статья будет предвзятой. Однако у меня есть веские причины не любить этот подход.</p><p>Критика ООП — очень деликатная тема. Вероятно, она заденет многих читателей. Тем не менее, я делаю то, что считаю правильным. Цель — не обидеть, а повысить осведомлённость о проблемах ООП. Здесь нет критики ООП Алана Кея — он гений. Было бы замечательно, если бы эта парадигма была реализована так, как он это задумал. Я критикую современный подход Java/C#.</p><p>Думаю, что это неправильно — де-факто считать ООП стандартом для организации кода многими людьми, в том числе на очень высоких технических должностях. Также недопустимо, что многие основные языки не предлагают никаких альтернатив организации кода, кроме ООП.</p><p>Я перебарывал себя, работая над ООП-проектами. И я не имел ни малейшего понятия, почему я так напрягался. Может быть, я был недостаточно хорош? Может, мне нужно было выучить ещё несколько шаблонов проектирования? Так я думал, и в конце концов полностью выгорел.</p><p>Эта статья подводит итог моего первого десятилетнего пути из объектно-ориентированного в функциональное программирование. Как бы я ни старался, я больше не мог найти варианты использования для ООП. Я лично видел, как ООП-проекты терпят крах, потому что их становится слишком сложно обслуживать.</p><h2>В чём суть?</h2><p>Объектно-ориентированные программы предлагаются в качестве альтернативы правильным.</p><p>Объектно-ориентированное программирование было создано с одной целью — управлять сложностью процедурных кодовых баз. Другими словами, такой подход должен был улучшить организацию кода. Нет объективных и открытых доказательств того, что ООП лучше простого процедурного программирования.</p><p>Однако горькая правда в том, что ООП терпит неудачу в единственной задаче, для которой оно было предназначено. На бумаге это выглядит хорошо, но как только сложность приложения начинает увеличиваться, всё становится плохо. Вместо уменьшения сложности этот подход поощряет беспорядочное совместное использование изменяемого состояния и вносит дополнительную сложность своими многочисленными шаблонами проектирования. ООП делает общие практики, такие как рефакторинг и тестирование, неоправданно сложными.</p><p>Некоторые могут не согласиться со мной, но современное ООП в Java/C# никогда не было спроектировано должным образом. Оно никогда не было результатом работы хорошего исследовательского института (в отличие от Haskell/FP). Лямбда-исчисление предлагает полную теоретическую основу для функционального программирования. ООП не имеет ничего соответствующего этому. Использование ООП невинно в краткосрочной перспективе, особенно в начальных проектах. Но в долгосрочной перспективе это бомба замедленного действия, которая может взорваться, когда кодовая база станет достаточно большой. Проекты откладываются, сроки горят, разработчики перегорают, добавление новых функций становится практически невозможным. Организация помечает код как «легаси», а команда разработчиков планирует переписать его.</p><p>ООП не является естественным для человеческого мозга. Мыслительный процесс сосредоточен на том, чтобы «делать» вещи — гулять, разговаривать с другом, есть пиццу. Мозг развился, чтобы делать вещи, а не организовывать мир в сложные иерархии абстрактных объектов.</p><p>ООП-код является недетерминированным. В отличие от функционального программирования, нет гарантий в получении одинакового вывода при одинаковых входных данных. Это делает анализ программы очень сложным. В качестве упрощённого примера: вывод 2 + 2 или calculator.Add(2, 2) в основном равен четырём, но иногда он может становиться равным трём, пяти и даже 1004. Зависимости объекта Calculator могут изменить результат вычисления трудно уловимым, но основательным способом.</p><h2>Необходимость в стабильном фреймворке</h2><p>Хорошие программисты пишут хороший код, а плохие — плохой независимо от парадигмы программирования. Однако эта парадигма должна ограничивать плохих программистов от нанесения слишком большого ущерба. Конечно, вы не плохой программист, так как уже читаете эту статью и стараетесь учиться. Плохие никогда не успевают учиться, они просто нажимают кнопки на клавиатуре как сумасшедшие. Нравится вам это или нет, но вы будете работать с такими людьми. И, к сожалению, ООП не имеет достаточных ограничений, которые бы помешали плохим программистам нанести слишком большой ущерб.</p><p>Я не считаю себя плохим программистом. Но даже я не могу написать хороший код без сильного фреймворка, на котором можно было бы основать работу. Речь не идёт о софтверных фреймворках. Здесь подразумевается более общее словарное определение фреймворка — «необходимая несущая конструкция». Это фреймворки, которые регулируют более абстрактные вещи, такие как организация кода и уменьшение сложности кода. Хоть объектно-ориентированное и функциональное программирование являются парадигмами программирования, они также являются высокоуровневыми фреймворками.</p><h3>Ограничение выбора</h3><p>C++ — ужасный [объектно-ориентированный] язык… Ограничение вашего проекта до C означает, что люди не напортачат ни с какой идиотской «объектной моделью».</p><p>Линус Торвальдс широко известен своей открытой критикой C++ и ООП. Одна вещь, в которой он был на 100 % прав — это необходимость ограничения программистов в выборе. На самом деле, чем меньше у программистов выбора, тем более устойчивым становится их код. В приведённой выше цитате Торвальдс настоятельно рекомендует иметь хороший фреймворк, на котором будет основан ваш код.</p><p>Многим не нравятся ограничения скорости на дорогах, но они необходимы, чтобы не дать людям разбиться насмерть. Точно так же хорошая среда программирования должна обеспечивать механизмы, которые мешают делать глупости. Хорошая среда помогает писать надёжный код. Прежде всего, это должно помочь уменьшить сложность, предоставляя следующие вещи:</p><ul><li>модульность и возможность повторного использования;</li><li>правильная изоляция состояния;</li><li>высокое <a href="https://ru.wikipedia.org/wiki/%D0%9E%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB/%D1%88%D1%83%D0%BC">отношение сигнал/шум</a>.</li></ul><p>ООП предоставляет разработчикам слишком много инструментов и вариантов, не налагая правильных ограничений. Несмотря на обещания ООП рассмотреть модульность и улучшить возможность повторного использования, оно не выполняет свои обещания (подробнее об этом позже). ООП-код поощряет использование разделяемого изменяемого состояния, которое может быть небезопасно от раза к разу. ООП обычно требует большого количества бойлерплейта (низкого отношения сигнал/шум).</p><h3>Функциональное программирование</h3><p>Что такое функциональное программирование (ФП)? Некоторые люди считают, что это очень сложная парадигма организации кода, которая применима только в академических кругах и не подходит для «реального мира». Функциональное программирование имеет прочную математическую основу и берёт начало в <a href="https://ru.wikipedia.org/wiki/Лямбда-исчисление">лямбда-исчислениях</a>. Большинство его идей возникли как ответ на слабые стороны более распространённых языков программирования. Функции являются основной абстракцией функционального программирования. При правильном использовании функции обеспечивают такой уровень модульности и возможности повторного использования кода, какой никогда не встречался в ООП. ФП даже имеет шаблоны проектирования, которые решают проблемы null-значений и обеспечивают превосходную обработку ошибок.</p><p>Одну вещь ФП делает действительно хорошо — помогает писать надёжное программное обеспечение. Потребность в отладчике почти полностью исчезает. Нет необходимости проверять весь код и смотреть переменные. Лично я не трогал отладчик в течение очень долгого времени. При этом, если вы знаете, как использовать функции, вы уже функциональный программист. Вам просто нужно узнать, как использовать их наилучшим образом.</p><p>Я не проповедую функциональное программирование, мне всё равно, какую парадигму вы используете при написании кода. Я пытаюсь рассказать о механизмах, которые предоставляет функциональное программирование для решения проблем, присущих ООП / императивному программированию.</p><h2>Мы всё не так поняли про ООП</h2><p>Мне очень жаль, что давным давно я ввёл термин «объекты», потому что они заставляют многих людей сосредоточиться на менее важной идее. Основная же идея — обмен сообщениями.</p><p>Erlang, вероятно, — <a href="https://stackoverflow.com/questions/3431509/is-erlang-object-oriented/3433808#3433808">единственный</a> популярный объектно-ориентированный язык, хоть и обычно не считается таковым. Конечно, Smalltalk — это тоже чистый ООП-язык, однако он не используется широко. И Smalltalk, и Erlang используют ООП так, как это было задумано его изобретателем Аланом Кеем.</p><h3>Обмен сообщениями</h3><p>Алан Кей ввёл термин «объектно-ориентированное программирование» в 1960-х годах. Он имел опыт работы в области биологии и пытался заставить компьютерные программы общаться так же, как живые клетки. Основная идея состояла в том, чтобы независимые программы (ячейки) общались, отправляя друг другу сообщения. Состояние независимых программ никогда бы не открылось внешней среде (инкапсуляция). Вот оно. ООП никогда не предназначался для наследования, полиморфизма, ключевого слова new и множества шаблонов проектирования.</p><h3>ООП в чистом виде</h3><p>Erlang — это ООП в чистом виде. В отличие от других популярных языков, он сосредоточен на основной идее ООП — обмен сообщениями. В Erlang объекты взаимодействуют, передавая между собой неизменяемые сообщения. Есть ли доказательства того, что неизменяемые сообщения лучше методов? Да, чёрт возьми! Erlang самый надёжный язык в мире. Он поддерживает большую часть мировой телекоммуникационной инфраструктуры, включая интернет. Некоторые из систем, написанных на Erlang, имеют надежность 99,9999999 % (вы правильно прочитали — девять девяток).</p><h2>Сложность кода</h2><p>Благодаря объектно-ориентированным языкам программирования, компьютерное программное обеспечение становится более многословным, менее читаемым, менее наглядным и более сложным для модификации и обслуживания.</p><p>Наиболее важным аспектом разработки ПО является снижение сложности кода. Ни одна из фич не имеет значения, если кодовую базу становится невозможно поддерживать. В этом случае даже стопроцентное тестовое покрытие ничего не стоит.</p><p>Что делает кодовую базу сложной? Есть много вещей, на которые следует обратить внимание. На мой взгляд, главными причинами являются: общее изменяемое состояние, ошибочные абстракции и низкое отношение сигнал/шум (бойлерплейт). Все они распространены в ООП.</p><h2>Проблемы состояния</h2><p>Состояние — это любые временные данные, хранящиеся в памяти: переменные или поля/свойства в ООП. Императивное программирование (включая ООП) описывает вычисления с точки зрения состояния программы и изменений в этом состоянии. Декларативное (функциональное) программирование описывает желаемые результаты и не указывает явно изменения состояния.</p><h3>Изменчивое состояние — акт умственного жонглирования</h3><p>Я думаю, что большие объектно-ориентированные программы борются со сложностью, возрастающей по мере построения большого графа изменяемых объектов. Понимаете, это как пытаться понять и держать в голове, что произойдет при вызове метода и какими будут побочные эффекты.</p><p>Состояние само по себе довольно безобидно. Но изменчивое состояние — большая угроза стабильности. Особенно, если его распространять. Что именно является изменчивым состоянием? Любое состояние, которое может измениться: переменные или поля в ООП.</p><p>Рассмотрим пример из реального мира. У вас есть чистый кусок бумаги, вы пишете на нём заметку. В результате вы получаете тот же кусок в другом состоянии (текст). Вы фактически изменили его состояние. Это вполне нормально в реальном мире, поскольку никто не заботится об этом куске бумаги. Если только это не оригинальная картина «Мона Лиза».</p><h3>Ограничения человеческого мозга</h3><p>Почему изменчивое состояние такая большая проблема? Человеческий мозг — самая мощная машина в известной вселенной. Но он очень плохо работает с состоянием, поскольку мы можем удерживать в рабочей памяти только 5 сущностей за раз. Гораздо проще рассуждать о куске кода, если вы думаете только о том, что он делает, а не о том, какие переменные он изменяет вокруг кодовой базы.</p><p>Программирование с изменяемым состоянием — это акт умственного жонглирования. Делать это двумя шарами довольно просто, но взяв три или больше, я уроню их все. С написанием кода так же. Я стал намного продуктивнее, а мой код стал намного надёжнее, как только я отбросил изменчивое состояние. Почему мы тогда пытаемся выполнять этот акт умственного жонглирования каждый день на работе? К сожалению, умственное манипулирование изменчивым состоянием лежит в основе ООП. Единственная цель существования методов объекта состоит в том, чтобы видоизменить этот же объект.</p><h3>Разрозненное состояние</h3><p>ООП ещё больше усугубляет проблему организации кода, разбрасывая состояние по всей программе. Рассеянное состояние затем беспорядочно распределяется между различными объектами.</p><p>Рассмотрим пример из реального мира. Забудем на секунду, что мы все взрослые. Притворимся, что пытаемся собрать крутой грузовик из Lego. Но здесь есть одна загвоздка — все детали грузовика случайно смешаны с деталями других ваших игрушек Lego. И они были разложены в 50 разных коробках, снова случайно. И вам не разрешают группировать детали вашего грузовика. Вы должны держать в голове, где находятся различные его части, и можете вынимать их только одну за другой. Да, вы в конечном счёте соберёте этот грузовик, но сколько времени это займет?</p><p>Какое это имеет отношение к программированию? В функциональном программировании состояние обычно является изолированным. Вы всегда знаете, откуда оно исходит. Состояние никогда не разбросано по разным функциям. В ООП каждый объект имеет своё состояние. При построении программы вы должны иметь в виду состояние всех объектов, с которыми вы в данный момент работаете. Чтобы облегчить жизнь, лучше всего иметь очень небольшую часть кода, связанную с состоянием. Пусть основные части вашего приложения не будут содержать состояния и будут чистыми. Это является главной причиной успеха для Flux-паттерна в фронтенде (он же <a href="https://ru.wikipedia.org/wiki/Redux">Redux</a>).</p><h3>Беспорядочно разделённое состояние</h3><p>Изменчивое состояние в реальном мире почти никогда не является проблемой, поскольку вещи хранятся в частном порядке. Это «правильная инкапсуляция» на работе. Представьте художника, который работает над следующей картиной Моны Лизы. Он работает над картиной один. Заканчивает, а затем продаёт свой шедевр за миллионы. А потом он решает устроить рисовальную вечеринку и приглашает своих друзей — эльфа, Гэндальфа, полицейского и зомби. Все они начинают рисовать на одном и том же холсте одновременно. Конечно, ничего хорошего из этого не выйдет.</p><p>Общее изменяемое состояние не имеет смысла в реальном мире. Но это именно то, что происходит в программах ООП — состояние беспорядочно разделяется между различными объектами и они изменяют его любым удобным для них способом. Это, в свою очередь, делает анализ программы всё сложнее, так как кодовая база продолжает расти.</p><h3>Проблемы параллелизма</h3><p>Беспорядочное совместное использование изменяемого состояния в ООП делает распараллеливание такого кода практически невозможным. Для решения этой проблемы были изобретены сложные механизмы: блокировка потоков, мьютекс и многие другие. У таких сложных подходов есть свои недостатки — взаимоблокировки, отсутствие возможности компоновки, плюс отладка многопоточного кода очень сложна и отнимает много времени. Речь даже не идёт об увеличении сложности, вызванном использованием таких механизмов параллелизма.</p><h3>Не все состояния — зло</h3><p>Вероятно, изменение состояний по задумке Алана Кея — не зло. Оно, вероятно, полезно, если действительно изолировано (не как в ООП). Также вполне нормально иметь неизменяемые объекты передачи данных. Ключ здесь «неизменяемые». Такие объекты затем используются для передачи данных между функциями.</p><p>Однако такие объекты также делают методы и свойства ООП избыточными. Какая польза от наличия методов и свойств объекта, если его нельзя изменить?</p><h3>Изменчивость неотъемлема в ООП</h3><p>Некоторые утверждают, что изменяемое состояние — решение разработчиков в ООП, а не данность. Но это не так. Это не выбор разработчиков, а практически единственный вариант. Да, неизменяемые объекты можно передавать в методы Java/C#. Но это делается редко, поскольку большинство разработчиков по умолчанию используют изменение данных. Даже если разработчики пытаются использовать неизменяемость в своих программах ООП, языки не предоставляют встроенных механизмов для неизменяемости и для эффективной работы с неизменяемыми данными (то есть постоянными структурами данных).</p><p>Можно сделать так, что объекты будут общаться только путём передачи неизменяемых сообщений и никогда не будут передавать никакие ссылки (что на самом деле редкость). Такие программы будут более надёжными, чем основные в ООП. Но объекты всё ещё должны изменить своё собственное состояние после получения сообщения. Сообщение является побочным эффектом, и его единственная цель — вызвать изменения. Сообщения были бы бесполезны, если бы они не могли изменить состояние других объектов.</p><p>Невозможно использовать ООП, не вызывая изменения состояния.</p><h3>Троянский конь инкапсуляции</h3><p>Часто упоминается, что инкапсуляция является одним из величайших преимуществ ООП. Предполагается защитить внутреннее состояние объекта от внешнего доступа. Но есть небольшая проблема. Это не работает.</p><p>Инкапсуляция — это троянский конь ООП. Он продвигает идею общего изменяемого состояния, делая его, казалось бы, безопасным. Инкапсуляция позволяет небезопасному коду проникать в кодовую базу (и даже поощряет это), заставляя её гнить изнутри.</p><h3>Проблема глобального состояния</h3><p>Часто твердят, что глобальное состояние является корнем всех бед и его следует избегать любой ценой. Инкапсуляция, по сути, является глобальным состоянием. Чтобы код был более эффективным, объекты передаются не по значению, а по ссылке. Вот где «внедрение зависимости» падает в грязь лицом.</p><p>Позвольте объяснить. Всякий раз, когда создаётся объект, ссылки на его зависимости передаются конструктору. Эти зависимости также имеют своё внутреннее состояние. Вновь созданный объект хранит ссылки на эти зависимости в своём внутреннем состоянии, а затем изменяет их любым удобным для него способом. Он также передаёт эти ссылки на всё остальное, что может в конечном счёте использовать. Это создаёт сложный граф разнородных общих объектов, которые изменяют состояние друг друга. Это, в свою очередь, вызывает огромные проблемы. Становится почти невозможно увидеть, что вызвало изменение состояния программы. Дни могут быть потрачены впустую в попытках отладить такие изменения. И вам повезёт, если не придётся иметь дело с параллелизмом (подробнее об этом позже).</p><h3>Методы/Свойства</h3><p>Методы или свойства, которые обеспечивают доступ к определённым полям, не лучше, чем непосредственное изменение значения поля. Не имеет значения, изменяете ли вы состояние объекта с помощью необычного свойства или метода, результат один и тот же — изменённое состояние.</p><h2>Проблема с моделированием реального мира</h2><p>Некоторые утверждают, что ООП пытается смоделировать реальный мир. Это неправда — ООП не имеет ничего общего с реальным миром. Попытка смоделировать программы как объекты является одной из самых больших ошибок ООП.</p><h3>Реальный мир не иерархичен</h3><p>ООП пытается моделировать всё как иерархию объектов. Но это не так. Объекты в реальном мире взаимодействуют друг с другом с помощью сообщений, но в основном они независимы друг от друга.</p><h3>Наследование в реальном мире</h3><p>ООП-наследование не отражает наследование реального мира. Родительский объект не может изменить поведение дочерних объектов во время выполнения. Даже если вы наследуете свою ДНК от родителей, они не могут вносить изменения в вашу ДНК по своему усмотрению. Вы не наследуете поведение от своих родителей. Вы развиваете своё поведение. И вы не можете переопределить поведение своих родителей.</p><h3>В реальном мире нет методов</h3><p>Имеет ли лист бумаги, на котором вы пишете, метод «записи»? Нет. Вы просто берёте пустой лист бумаги, берёте ручку и пишете текст. Вы, как человек, тоже не имеете метода «записи» — вы принимаете решение написать какой-то текст на основе внешних событий или ваших внутренних мыслей.</p><h2>Королевство Существительных</h2><p>Объекты связывают функции и структуры данных вместе в неделимых единицах. Я думаю, что это фундаментальная ошибка, поскольку функции и структуры данных принадлежат совершенно разным мирам.</p><p>Объекты (или существительные) находятся в самом центре ООП. Основное ограничение ООП состоит в том, что он превращает всё в объекты. Но не всё должно быть смоделировано как существительные. Операции (функции) не должны моделироваться как объекты. Зачем создавать класс Multiplier, когда всё, что нужно, — функция, умножающая два числа? Просто сделайте функцию Multiply. Пусть данные будут данными, а функции будут функциями.</p><p>В языках, отличных от ООП, выполнять тривиальные задачи вроде сохранения данных в файл очень просто. Это похоже на то, как вы описали бы действие на простом английском языке.</p><p>Рассмотрим ситуацию из реального мира. Возьмём того же художника. Пусть он владеет фабрикой рисования (PaintingFactory). Он нанял менеджера по кистям (BrushManager), менеджера по цвету (ColorManager), менеджера по холсту (CanvasManager) и поставщика Моны Лизы (MonaLisaProvider). Его хороший друг зомби использует стратегию потребления мозга (BrainConsumingStrategy). Эти объекты, в свою очередь, определяют следующие методы: создать картину (CreatePainting), найти кисть (FindBrush), выбрать цвет (PickColor), вызвать Мону Лизу (CallMonaLisa) и потреблять мозги (ConsumeBrainz).</p><p>Конечно, это чушь. Это никогда не могло бы произойти в реальном мире. Сколько ненужной сложности было создано для простого акта рисования картины? Нет необходимости изобретать странные концепции для хранения ваших функций, когда им разрешено существовать отдельно от объектов.</p><h2>Модульное тестирование</h2><p>Автоматическое тестирование является важной частью процесса разработки и очень помогает в предотвращении регрессий (ошибок, вносимых в существующий код). Модульное тестирование играет огромную роль в процессе автоматического тестирования.</p><p>Некоторые могут не согласиться, но ООП-код общеизвестно труден для модульного тестирования. Модульное тестирование предполагает изоляцию, и чтобы создать метод для такого вида тестирования, нужно:</p><ul><li>извлечь его зависимости в отдельный класс;</li><li>создать интерфейс для вновь созданного класса;</li><li>объявить поля для хранения экземпляра вновь созданного класса;</li><li>использовать фреймворк, чтобы «mock-ать» зависимости;</li><li>использовать специальный фреймворк для внедрения зависимостей.</li></ul><p>Сколько ещё препятствий нужно преодолеть, чтобы сделать фрагмент кода тестируемым? Сколько времени было потрачено впустую? Кроме того, нужно создавать экземпляр всего класса, чтобы протестировать один метод. Это подтянет код из всех его родительских классов. С ООП писать тесты для унаследованного кода ещё сложнее, практически невозможно. Целые компании были созданы (<a href="https://www.typemock.com/">TypeMock</a>) из-за проблемы тестирования легаси-кода.</p><h3>Шаблонный код</h3><p>Шаблонный код (бойлерплейт) является самой большой проблемой, когда речь идёт о соотношении сигнал/шум. Шаблонный код — это «шум» для компиляции программы. Такой код требует времени для написания и делает кодовую базу менее читаемой.</p><p>Хоть в ООП пропагандируется «программа для интерфейса, а не для реализации», не всё должно становиться интерфейсом. Следовало бы прибегнуть к использованию интерфейсов во всей кодовой базе с единственной целью — тестирование. Также, вероятно, пришлось бы использовать внедрение зависимостей, что в дальнейшем привело бы к ненужной сложности.</p><h3>Тестирование приватных методов</h3><p>Некоторые утверждают, что приватные методы не должны тестироваться. Я склонен не согласиться, модульное тестирование называется «модульным», так как тестируются небольшие изолированные блоки кода. Тем не менее, тестирование приватных методов в ООП практически невозможно. Не следует делать приватные методы внутренними только ради тестирования.</p><p>Чтобы достичь тестируемости частных методов, их обычно извлекают в отдельный объект. Это, в свою очередь, вносит ненужную сложность и шаблонный код.</p><h2>Рефакторинг</h2><p>Рефакторинг является важной частью работы разработчика. По иронии судьбы ООП-код трудно реорганизовать. Рефакторинг должен сделать код менее сложным и более понятным. Напротив, реорганизованный код в ООП становится значительно более сложным. Чтобы сделать код тестируемым, нужно использовать внедрение зависимостей и создать интерфейс для реорганизованного класса. И даже тогда рефакторинг ООП-кода сложен без специальных инструментов, таких как Resharper.</p><p>В этом простом примере количество строк увеличилось более чем в два раза только для извлечения одного метода. Почему рефакторинг создаёт ещё большую сложность, когда код подвергается рефакторингу, который нужен в первую очередь для уменьшения сложности?</p><p>Сравните это с аналогичным рефакторингом не-ООП-кода в JavaScript:</p><p>Код буквально остался прежним. Функция isValidInput() просто переместилась в другой файл и добавилась одна строка для импорта этой функции. Также добавилась _isValidInput() к сигнатуре функции для удобства тестирования.</p><p>Это простой пример, но на практике сложность ООП-кода возрастает экспоненциально по мере увеличения кодовой базы. И это ещё не всё. Рефакторинг ООП-кода крайне рискован. Сложные графики зависимостей и состояния, разбросанные по всей кодовой базе ООП, не позволяют человеческому мозгу рассмотреть все потенциальные проблемы.</p><h2>Костыли в ООП</h2><p>Что вы делаете, когда что-то не работает? Обычно есть два варианта — выбросить или попробовать исправить. ООП не может быть легко выброшено: миллионы разработчиков обучаются ООП, миллионы организаций по всему миру используют его. В течение десятилетий люди много думали, пытаясь решить проблемы, распространённые в ООП-коде. И они придумали множество шаблонов проектирования.</p><h3>Шаблоны проектирования</h3><p>ООП предоставляет набор рекомендаций, которые теоретически должны позволить разработчикам постепенно наращивать всё большие и большие системы: принцип SOLID, внедрение зависимостей, шаблоны проектирования и т. д. К сожалению, шаблоны проектирования — это не что иное, как костыли. Они существуют исключительно для устранения недостатков ООП. На эту тему было написано множество книг. И они не были бы такими плохими, если бы не вносили огромную сложность в кодовые базы.</p><h3>Фабрика проблем</h3><p>Фактически невозможно написать хороший и поддерживаемый объектно-ориентированный код.</p><p>На одной стороне спектра есть кодовая база, которая является непоследовательной и не придерживается каких-либо стандартов. По другую сторону — башня с чрезмерно сконструированным кодом, куча ошибочных абстракций, построенных одна поверх другой. Шаблоны проектирования очень помогают при построении таких башен абстракций.</p><p>Вскоре добавление новой функциональности и даже понимание всей сложности становится всё труднее и труднее. Кодовая база будет полна таких вещей, как SimpleBeanFactoryAwareAspectInstanceFactory, AbstractInterceptorDrivenBeanDefinitionDecorator, TransactionAwarePersistenceManagerFactoryProxy или RequestProcessorFactoryFactory.</p><p>Нужно тратить драгоценные интеллектуальные ресурсы, пытаясь понять башню абстракций, которую создали сами разработчики. Отсутствие структуры во многих случаях лучше, чем плохая структура.</p><figure><img src="https://media.tproger.ru/uploads/2019/08/1__xDSrTC0F2lke6OYtkRm8g.png" alt="" /></figure><p>Статья на тему: <a href="https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition">FizzBuzzEnterpriseEdition</a>.</p><h2>Падение четырёх столпов ООП</h2><p>Четыре столпа ООП: абстракция, наследование, инкапсуляция и полиморфизм.</p><p>Посмотрим, что они из себя представляют на самом деле, один за другим.</p><h3>Наследование</h3><p>Я думаю, что невозможность повторного использования затрагивает объектно-ориентированные, а не функциональные языки. Поскольку проблема с ООП-языками заключается в том, что у них есть вся эта неявная среда, которую они носят с собой. Вы хотели банан, а получили гориллу с бананом и целые джунгли в придачу.</p><p>Наследование не имеет ничего общего с реальным миром. Оно является худшим способом достижения повторного использования кода. <a href="https://ru.wikipedia.org/wiki/Design_Patterns">Банда четырёх</a> недвусмысленно рекомендовала отдавать предпочтение композиции перед наследованием, а <a href="https://ru.wikipedia.org/wiki/Go">некоторые современные языки программирования</a> вообще его избегают.</p><p>Есть несколько проблем с наследованием:</p><ul><li>происходит добавление большого количества кода, который даже не нужен вашему классу (проблема с бананом и джунглями);</li><li>определение частей вашего класса где-либо ещё затрудняет анализ кода, особенно с несколькими уровнями наследования;</li><li>в большинстве языков множественное наследование даже невозможно, это в основном делает наследование бесполезным в качестве механизма совместного использования кода.</li></ul><h3>Полиморфизм</h3><p>Полиморфизм великолепен, он позволяет изменять поведение программы во время выполнения. Это очень базовая концепция в компьютерном программировании. Я не очень уверен, почему в ООП ему уделяют так много внимания. Он выполняет свою работу, но в очередной раз приводит к умственному жонглированию. Это делает кодовую базу значительно более сложной. Анализ конкретного метода, который вызывается, становится очень трудным.</p><p>Функциональное программирование позволяет добиться того же полиморфизма гораздо более элегантным способом — просто передав функцию, определяющую желаемое поведение во время выполнения. Что может быть проще этого? Не нужно определять кучу перегруженных абстрактных виртуальных методов в нескольких файлах (и интерфейсе).</p><h3>Инкапсуляция</h3><p>Как обсуждалось ранее, инкапсуляция — троянский конь ООП. На самом деле это глобальное изменяемое состояние, благодаря которому небезопасный код выглядит безопасным. Небезопасная практика написания кода — это основа, на которую программисты ООП полагаются в своей повседневной работе.</p><h3>Абстракция</h3><p>Абстракция в ООП пытается решить сложность, скрывая ненужные детали от программиста. Теоретически, это должно позволить разработчику анализировать кодовую базу, не думая о скрытой сложности.</p><p>Причудливое слово для простой концепции. В процедурных/функциональных языках вы можете просто «спрятать» детали реализации в соседнем файле. Нет необходимости называть этот основной акт «абстракцией».</p><p>Для подробной информации о столпах ООП можете прочитать статью: <a href="https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53">«Goodbye, Object-Oriented Programming»</a>.</p><h2>Почему ООП доминирует в индустрии?</h2><p>Ответ прост: рептилоидная инопланетная раса вступила в сговор с АНБ (и русскими), чтобы замучить вас (программистов) до смерти.</p><p>А если серьезно, то, вероятно, ответ — Java.</p><p>Java — самая неприятная вещь, случившаяся с компьютерами со времен MS-DOS.</p><h3>Java был прост</h3><p>Когда он был впервые представлен в 1995 году, Java был очень простым языком программирования по сравнению с альтернативами. В то время входной барьер для написания настольных приложений был высок. Разработка настольных приложений включала написание низкоуровневых API-интерфейсов win32 на С. Разработчики также должны были заниматься ручным управлением памятью. Другой альтернативой был Visual Basic, но многие не хотели замыкаться в экосистеме Microsoft.</p><p>Когда появился Java, для многих разработчиков работать с ним было просто. Он был бесплатным и мог использоваться на всех платформах. Такие вещи, как встроенная сборка мусора, понятные API-интерфейсы (по сравнению с загадочными win32-API), правильные пространства имён и знакомый C-подобный синтаксис сделали Java ещё более доступным.</p><p>GUI-программирование также становилось всё более популярным. Казалось, что различные компоненты пользовательского интерфейса хорошо отображаются на классы. Автозаполнение метода в IDE также заставило людей утверждать, что ООП API проще в использовании.</p><p>Возможно, Java не был бы таким плохим, если бы он не вынуждал разработчиков реализовывать ООП. Всё остальное в Java выглядело довольно хорошо. Сборка мусора, переносимость, функции обработки исключений, которых не хватало в других основных языках, — всё это было очень удобным.</p><h3>Затем появился C#</h3><p>Первоначально Microsoft в значительной степени опиралась на Java. Когда что-то начало идти не так (и после долгой юридической битвы с Sun Microsystems за лицензирование Java), Microsoft решила инвестировать в свою собственную версию Java. Так появился C# 1.0. C# как язык всегда считался «лучшей версией Java». Однако есть одна огромная проблема — это тот же язык ООП с теми же недостатками, скрытыми под слегка улучшенным синтаксисом.</p><p>Microsoft вкладывала значительные средства в свою экосистему .NET, которая также включала в себя хорошие инструменты для разработчиков. В течение многих лет Visual Studio была одной из лучших доступных сред IDE. Это привело к широкому распространению платформы .NET, особенно на предприятиях.</p><p>В последнее время Microsoft вкладывает значительные средства в экосистему браузера, продвигая свой TypeScript. TypeScript великолепен, потому что он может компилировать чистый JavaScript и добавляет такие вещи, как статическая проверка типов. Но уже не так великолепно отсутствие надлежащей поддержки функциональных конструкций: нет встроенных неизменяемых структур данных, нет композиции функций, нет правильного сопоставления с образцом. TypeScript является в первую очередь ООП-языком. В основном это C# для браузеров. Даже отвечал за разработку и C#, и TypeScript один человек — <a href="https://ru.wikipedia.org/wiki/Хейлсберг,_Андерс">Андерс Хейлсберг</a>.</p><h3>Функциональные языки</h3><p>Функциональные языки никогда не поддерживались такими крупными компаниями, как Microsoft. F# не считается, так как инвестиции были незначительными. Разработка функциональных языков в основном осуществляется сообществом. Это объясняет различия в популярности между объектно-ориентированными и функциональными языками.</p><h2>Пора двигаться дальше?</h2><p>Теперь мы знаем, что ООП — эксперимент, который провалился. Настало время двигаться дальше. Настало время нам, как сообществу, признать эту идею провальной и отказаться от неё.</p><p>Думаю, довольно легко продолжать использовать то, что использовали десятилетиями. Большинство людей никогда не пробовало функциональное программирование. А те, кто попробовал (как и я), никогда не смогут вернуться к написанию ООП-кода.</p><p>Генри Форд однажды сказал: «Если бы я спросил людей, чего они хотят, они бы ответили — более быстрых лошадей». В мире программного обеспечения большинство людей захотят «лучший ООП-язык». Люди могут легко описать пожелания, которые у них есть (более организованная и менее сложная кодовая база), но не лучшее решение.</p><h2>Каковы альтернативы?</h2><p>Внимание, спойлер — функциональное программирование.</p><p>Если термины вроде «функтор» или «монада» вызывают у вас некоторое беспокойство, то вы не одиноки. Функциональное программирование не было бы таким страшным, если бы оно дало более интуитивные названия некоторым из его концепций. Функтор — то, что можно преобразовать с помощью функции (вспомните list.map). Монада — вычисления, которые можно объединить в цепочку.</p><p>Используя функциональное программирование, вы станете более хорошим разработчиком. У вас будет время писать реальный код, который решает реальные проблемы, вместо размышлений об абстракциях и шаблонах проектирования.</p><p>Вы можете не осознавать этого, но вы уже функциональный программист, если используете функции в своей повседневной работе. Вам просто нужно узнать, как использовать их наилучшим образом.</p><p>Два великолепных функциональных языка с очень плавной кривой обучения — это <a href="https://elixir-lang.org/">Elixir</a> и <a href="https://elm-lang.org/">Elm</a>. Они позволяют разработчику сосредоточиться на важнейшем — написании надёжного программного обеспечения — одновременно устраняя все сложности, которые имеют более традиционные функциональные языки.</p><p>Какие есть ещё варианты? Ваша организация уже использует C#? Попробуйте F# — удивительный функциональный язык, обеспечивающий отличную совместимость с существующим кодом .NET. Используете Java? Тогда Scala или Clojure — хорошие альтернативы. Используете JavaScript? С правильным руководством и линтингом JavaScript может быть хорошим функциональным языком.</p><h2>Защитники ООП</h2><p>Реакция от защитников ООП вполне ожидаема. Они скажут, что эта статья полна неточностей. Некоторые могут даже начать называть имена. Они могли бы даже назвать меня junior-разработчиком без реального опыта ООП. Кто-то может сказать, что мои предположения ошибочны, а примеры бесполезны. Это не имеет значения.</p><p>Они имеют право на своё мнение. Однако их аргументы в защиту ООП обычно довольно слабы. По иронии судьбы, большинство из них никогда не программировали на настоящем функциональном языке. Как можно провести сравнение между двумя вещами, если вы никогда не пробовали их обе? Такие сравнения не очень хороши.</p><p>Закон Деметры не очень полезен — он ничего не делает для решения проблемы недетерминированности. Общее изменяемое состояние всё ещё является общим изменяемым состоянием, независимо от того, каким образом вы получаете доступ или изменяете его. Метод a.total() не сильно лучше a.getB().getC().total(). Это просто заметает проблему под ковёр.</p><p>Предметно-ориентированное проектирование? Это полезная методология разработки, она немного помогает со сложностью. Но она по-прежнему ничего не делает для решения фундаментальной проблемы общего изменяемого состояния.</p><h3>Просто инструмент в наборе</h3><p>Часто слышу мнение людей, что ООП — просто ещё один инструмент в наборе. Да, это такой же инструмент, как и то, что лошади и машины — инструменты для перевозки. В конце концов, все они служат одной цели. Зачем использовать машины, если можно продолжать ездить на старых добрых лошадях?</p><h3>История повторяется</h3><p>В начале 20-го века автомобили начали заменять лошадей. В 1900 году в Нью-Йорке было всего несколько автомобилей на дорогах, но в основном люди использовали для перевозки лошадей. Огромная индустрия была сосредоточена вокруг конного транспорта. Целые предприятия были созданы вокруг уборки навоза. <a href="https://99percentinvisible.org/article/cities-paved-dung-urban-design-great-horse-manure-crisis-1894/">Люди сопротивлялись переменам</a>. Они называли автомобили ещё одной «модой», которая в конечном итоге проходит, ведь лошади были здесь веками. Некоторые даже просили правительство вмешаться. В 1917 году на дорогах больше не осталось лошадей.</p><p>Насколько это актуально? Индустрия программного обеспечения сосредоточена вокруг ООП. Миллионы людей обучаются ООП и миллионы компаний используют его в своём коде. Конечно, они попытаются дискредитировать всё, что угрожает их хлебу с маслом. Это просто здравый смысл. Ясно видно, как история повторяется. В 20-м веке это были лошади против автомобилей, а в 21-м — объектно-ориентированное и функциональное программирование.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как использовать декораторы с фабричными функциями в JavaScript</title>
      <link>https://tproger.ru/translations/decorators-with-factory-functions</link>
      <comments>https://tproger.ru/translations/decorators-with-factory-functions?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/decorators-with-factory-functions</guid>
      <description><![CDATA[<p>Декораторы методов в JavaScript выносят побочные задачи вроде проверки авторизации и замера времени выполнения за пределы самого метода объекта.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/decorators-with-factory-functions">Как использовать декораторы с фабричными функциями в JavaScript</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 12 Nov 2018 10:58:20 GMT</pubDate>
      <content:encoded><![CDATA[<p>Декораторы методов дополняют объектно-ориентированное программирование, инкапсулируя функциональность, которую используют разные объекты.</p><p>Рассмотрим следующий код:</p><p>Задача метода add() — добавлять новые элементы во внутреннее состояние. Кроме того, метод должен проверить авторизацию пользователя и записать продолжительность выполнения. Эти две задачи второстепенны и могут повторяться в других методах.</p><p>Представим, что мы можем поместить решение этих задач в отдельные функции. Например, так:</p><p>Теперь метод add() просто добавляет новый элемент в список. Всё остальное решается декорированием метода.</p><p>logDuration() и authorize() — декораторы.</p><p>Декоратор функции — это функция высшего порядка, которая принимает в качестве аргумента одну функцию и возвращает другую, которая является вариацией функции-аргумента.</p><p><em>Реджинальд Брейтуэйт, «JavaScript Allongé»</em></p><h2>Логирование продолжительности</h2><p>Декораторы можно использовать, чтобы узнать, сколько выполнялся метод:</p><p>Обратите внимание, что декорируемая функция выполняется через передачу текущего значения this и всех аргументов: fn.apply(this, args).</p><h2>Авторизация</h2><p>Декоратор authorize() проверяет, есть ли у пользователя необходимые права для выполнения метода. Он сложнее предыдущего, так как зависит от другого объекта currentUser. В этом случае мы можем использовать функцию createAuthorizeDecorator() для создания декоратора. Он выполнит метод, только если пользователь авторизован:</p><p>Теперь можно создать декоратор и передать зависимости:</p><p>let authorize = createAuthorizeDecorator(currentUser);</p><h2>compose()</h2><p>Часто требуется применить несколько декораторов к одному методу. Простой способ сделать это — вызывать декораторы один за другим:</p><p>Другой способ — собрать все декораторы в один и применить новый декоратор к исходной функции. Можно использовать функцию compose() из библиотек вроде underscore.js.</p><p>При композиции функций одна функция применяется к результату другой.</p><p>Применение f() к результату g() означает compose(f, g)(x), что аналогично f(g(x)).</p><p>Композиция лучше всего работает с унарными функциями вроде наших декораторов. Унарная функция — это функция, принимающая один аргумент:</p><p>Ниже показано, как можно использовать compose() для применения декораторов на методе add():</p><h2>Порядок</h2><p>В некоторых случаях порядок применения декораторов имеет значение.</p><p>В нашем примере они применяются слева направо:</p><ol><li>Начинается логирование продолжительности вызова.</li><li>Выполняется авторизация.</li><li>Вызывается исходный метод.</li></ol><h2>Фабричные функции</h2><p>Фабричные функции лучше классов по следующим причинам:</p><ul><li>Инкапсуляция. Члены приватны по умолчанию, поэтому можно самостоятельно решить, какие методы предоставить публичному API. В классах все члены публичны.</li><li>Нет проблем с потерей контекста this. Фабричные функции не используют this, поэтому отсутствуют связанные с ним проблемы.</li><li>Безопасность объектов лучше. Внутреннее состояние инкапсулируется, и API неизменно. А, например, глобальный объект, объявленный с помощью class, можно изменить в консоли разработчика.</li></ul><p>Уберём самовыполняющуюся часть из паттерна Revealing Module и получим фабричную функцию. Ниже показано определение фабричной функции TodoStore():</p><h2>Декорирование фабричной функции</h2><p>Зачастую требуется применить декораторы на всех публичных методах объекта. Для этого можно создать функцию decorateMethods():</p><p>Теперь можно использовать функцию decorateAndFreeze() для декорирования всех публичных методов фабричной функции:</p><p>Декораторы работают как ключевые слова или аннотации — документируют поведение метода, но явно отделяют второстепенную логику от основной логики метода.</p><p><em>Реджинальд Брейтуэйт, «JavaScript Allongé»</em></p><h2>Сохранение контекста метода</h2><p>Если используется не фабричная функция, декоратор всё равно должен сохранять контекст исходного метода, чтобы не потерять значение this. Для этого нужно следовать двум правилам:</p><ol><li>Декоратор должен возвращать функцию с помощью ключевого слова function (никаких стрелочных функций).</li><li>Исходная функция должна вызываться с помощью call(this, ...) или apply(this, ...).</li></ol><p>Если им не следовать, то декоратор потеряет контекст this, однако фабричные функции будут работать, так как они не используют this.</p><p>Классы, функции-конструкторы и литералы объектов используют this, поэтому они потеряют контекст.</p><p>Взглянем на следующий пример:</p><h2>Вывод</h2><p>Фабричные функции и декораторы — мощные инструменты в арсенале разработчика. Фабричные функции создают ООП-объекты, а декораторы инкапсулируют общую логику, которую эти объекты могут повторно использовать. Эти две концепции дополняют друг друга.</p><p>Кроме того, декораторы улучшают читаемость. Код становится гораздо чище, так как он фокусируется на своей основной задаче и не отвлекается на второстепенную логику.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основы функционального программирования с примерами на Scala — часть 2</title>
      <link>https://tproger.ru/articles/scala-functional-programming-2</link>
      <comments>https://tproger.ru/articles/scala-functional-programming-2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/scala-functional-programming-2</guid>
      <description><![CDATA[<p>Вторая часть цикла о Scala посвящена гибкой ООП-модели языка — обжектам, трейтам, классам и кейс-классам — и специфичному синтаксическому сахару.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/scala-functional-programming-2">Основы функционального программирования с примерами на Scala — часть 2</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Scala]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 07 Oct 2018 17:38:16 GMT</pubDate>
      <content:encoded><![CDATA[<p>В прошлой <a href="https://tproger.ru/articles/scala-functional-programming-1/">статье</a> из нашей серии мы поговорили о том, зачем нужно функциональное программирование, и о недостатках императивного программирования, из-за которых функциональное набирает популярность.</p><p>Кроме того, мы привели базовое введение в Scala, мультипарадигменный язык программирования с широкой поддержкой функционального подхода. В предыдущей статье вы можете ознакомиться с базовыми понятиями языка и узнать, как настроить окружение.</p><p>В этой части мы расскажем об ООП-модели Scala и о некотором синтаксическом сахаре, специфичном для языка Scala.</p><h2>Основы модели ООП</h2><p>Язык Scala имеет достаточно гибкую ООП-модель, состоящую из обжектов, трейтов, классов и кейс-классов, чем-то похожую на таковую в Java.</p><p>Класс в Scala объявляется следующим образом:</p><p>и инстанцируется при помощи конструкции new ClassName(field1, field2, … , fieldN).</p><p>Параметры, введенные скобках после имени класса, являются private полями класса, однако можно изменить их область видимости: если нужно иметь доступ на чтение извне, перед именем поля нужно поставить модификатор val, а если нужно чтение и запись — модификатор var. Классы являются ссылочными типами и неявно наследуют базовый класс AnyRef. Поэтому присваивание объекта с мутирующими (var) полями другой переменной только копирует ссылку.</p><p>В статье будет идти речь о функциональном программировании, поэтому особенности языка, связанные с мутирующими переменными, выходят за рамки обсуждения, как и достаточно подробное описание взаимодействия всех возможных сущностей.</p><p>Внутри класса можно вводить многие вложенные структуры для внутреннего использования. По умолчанию все переменные, постоянные и методы, объявленные в теле класса, — публичные. Их можно защитить при помощи модификаторов private и protected. С другой стороны, вложенные классы недоступны извне.</p><p>Другой ООП-сущностью является trait. В отличие от класса, trait не может иметь конструктора и не может быть инстанцирован. Кроме того, он может содержать абстрактные методы, в то время как класс — только если помечен модификатором abstract.</p><p>Синтаксис трейта таков:</p><p>Ещё одна ООП-сущность, с которой вы столкнулись в <a href="https://tproger.ru/articles/scala-functional-programming-1/">предыдущей статье</a>, — object.</p><p>Его отличительной чертой является то, что он имеет единственный экземпляр, создаваемый автоматически, иначе говоря, является <a href="https://ru.wikipedia.org/wiki/Одиночка_(шаблон_проектирования)">синглтоном</a>. Только обжект может быть точкой входа в программу. Кроме того, вы можете импортировать его вложенные типы, методы, значения в область видимости другого файла. Это позволяет использовать обжект как средство структурной организации программы, давая возможность группировать константы и статические методы.</p><p>Наследование в Scala имеет некоторые особенности. Во-первых, практически всё может наследовать от нескольких trait при помощи синтаксиса extends trait1 with trait2 with trait3 ... with traitN.</p><p>Именно это явление вы видели в начале статьи:</p><p>Здесь App — предопределённый трейт, оборачивающий содержимое внутрь метода main.</p><p>Зачем нужен синтаксис extends … with … with …? Разве нельзя обойтись одним ключевым словом? Увы, нет. Если же разрешить множественное наследование (с наследованием реализации), то появляется <a href="https://ru.wikipedia.org/wiki/Ромбовидное_наследование">проблема ромбовидного наследования</a>. В Scala эта проблема решена определением главного трейта, реализация которого и наследуется. Этот главный трейт идёт первым после слова extends (к слову, это может быть вовсе не трейт, а класс или абстрактный класс).</p><p>Кроме того, вы не можете наследовать типы от объекта.</p><p>На самом деле, вы можете создать анонимный класс-наследник трейта, реализовав все его абстрактные методы используя функционал анонимных классов, и далее создать его экземпляр:</p><p>Но писать такой код бывает достаточно громоздко. Для этого были введены обжекты-компаньоны (companion object). Для них нет специального ключевого слова, поэтому, чтобы их определить, вам нужно создать в файле с классом (или трейтом) обжект с таким же именем, как у класса. У обжекта-комапньона вы можете определить метод apply:</p><p>Тогда вы можете создавать «экземпляры» трейта при помощи синтаксиса Foo.apply(1).</p><p>Этот код громоздкий, поэтому специально для метода apply сделали возможность не писать его название. Эта «магия» работает для всего что называется apply в любой сущности. Выглядит это так:</p><p>Foo(1)</p><p>Подобное можно делать и с классами. Отметим, что семантика метода apply может значительно отличаться от приведённого примера, ведь никто вас не ограничивает в том, что будет делать этот метод. Эта особенность активно используется как в стандартных библиотеках, так и в сторонних.</p><h2>Параметрические типы</h2><p>В Scala есть возможность параметризовать трейт, класс или его метод некоторым количеством типов. Делается это при помощи такого синтаксиса:</p><p>Вы можете использовать параметры типа внутри тела метода/класса/трейта разными способами. Например, вы можете написать функцию, которая будет вычислять композицию 2 функций:</p><p>def compose[U, V, W](f: U =&gt; V, g: V =&gt; W): W = (x :U) =&gt; g(f(x))</p><p>На этом примере мы можем продемонстрировать ещё одну особенность системы типов Scala — она может выводить параметры-типы шаблонов. Например, если мы введём две функции</p><p>то мы можем вычислить их композицию, не указывая цепочку типов:</p><p>val f = compose(fromIntToDouble, fromIntToString)</p><p>Что на самом деле эквивалентно:</p><p>val f: Int =&gt; String = compose[Int, Double, String](fromIntToDouble, fromIntToString)</p><p>Параметрические типы — мощное средство доказательства свойств программы — в том числе типобезопасности. Например, у вас в базе данных хранятся объекты двух типов A и B. Вы не хотите, чтобы кто-то мог добавить объект типа А в коллекцию элементов B (допустим, ваша БД нереляционная, например, MongoDB, и позволяет такое) или же не пытался записать элемент в несуществующую коллекцию. Вы можете написать метод добавления в коллекцию вот таким образом:</p><p>В этом примере вы сможете добавить элемент в коллекцию соответствующего типа. Попытка добавить элемент типа А в коллекцию элементов типа B вызовет ошибку компиляции, и, скорее всего, IDE предупредит вас об этом ещё на этапе написания кода.</p><p>Вы можете создавать псевдонимы для параметрических типов, если вы задали в них все или несколько параметров. К примеру, вы можете ввести список строк:</p><p>type String = List[String]</p><p>Или же, например, кортеж, один из параметров которого — целое:</p><p>type TypInt[T] = (T, Int)</p><p>Хотя на этом возможности системы типов Scala и не завершаются, дальнейшее повествование выходит за рамки нашего введения.</p><h2>Кейс-классы и сопоставление с шаблоном</h2><p>Оператор switch во многих языках воплощает задумку классификации данных на несколько категорий. Но большинство реализаций не могут поддерживать классификацию объектов, и поэтому редко используются в программах. В Scala есть очень мощная система сопоставления объекта с образцом. Однако экземпляр далеко не каждого класса можно сопоставлять с образцом без лишних телодвижений. Для упрощения этого процесса были введены два специальных вида сущностей: кейс-классы (case classes) и кейс-обжэкты (case objects). Кейс-классы подобны именованным кортежам, используемым для хранения данных. Часто они могут не иметь своих методов, выступая пассивными контейнерами, ведь Scala предоставляет великое множество других методов взаимодействия. Итак, синтаксис кейс-класса таков:</p><p>case class CaseClasName(parameterList) { /*опциональное тело класса*/ }</p><p>Все поля кейс-класса немутирующие и публичные. Это не является нарушением принципа инкапсуляции, поскольку он неприменим к публичным параметрам кейс-класса. Инкапсуляция полей имеет место, когда поля класса должны меняться некоторым сложным взаимосвязанным образом, и поэтому изменение полей должно производиться через методы класса. Здесь же нет никакого изменяемого состояния (мы крайне не рекомендуем его вводить в теле класса, чревато неожиданными последствиями), и инкапсулировать нечего. Инстанцировать экземпляр кейс-класса можно удобным синтаксисом:</p><p>CaseClasName(parameter1, parameter2, … , parameterN)</p><p>Примечание Здесь «под капотом» находится автоматически сгенерированный метод apply для этого класса, позволяющий пользоваться таким синтаксисом.</p><p>Пример:</p><p>Второй тип сущностей, case object, похож на case class, но не имеет полей. Зачем же он тогда нужен? Для создания строго типизированных перечислений.</p><p>Заметим, что кейс-класс не может наследовать от другого кейс-класса, поэтому рекомендуем держать иерархию данных как можно более плоской и как можно менее древоподобной. Всегда можно задать кейс-классам общий надтип и, соответственно, нужный набор полей при помощи трейтов. Например, можно сделать так:</p><p>Каким образом работает сопоставление с шаблоном (pattern matching)? При помощи следующего синтаксиса:</p><p>Шаблоны могут быть очень разнообразными в зависимости от используемого типа данных.</p><p>Кейс-класс в сопоставлении с шаблоном можно разложить на его параметры при помощи «вызова конструктора» этого кейс-класса. Например, приведенному выше классу User будет соответствовать шаблон User(name, email). Здесь переменные name и user извлекаются из объекта класса User и после этого становятся доступны блоку value, следующему за =&gt;. Кроме того, вы можете ввести переменную для обозначения объекта, сопоставляемого с образцом, при помощи символа @. Например, такой шаблон введёт переменную user для блока value:</p><p>user @ User(name, email)</p><p>Ещё одной полезной особенностью сопоставления с образцом является возможность зафиксировать некоторые поля класса или же, наоборот, игнорировать некоторые из них.</p><p>Например, мы можем выбирать пользователя только с именем «dave» шаблоном User("dave", email). Если же нам безразличен email пользователя, мы можем не вводить переменную для этого поля шаблоном User(name, _).</p><p>Стоит отметить, что сопоставление с образцами происходит в порядке их расположения. Если шаблон User(name, email) находится выше шаблона User("dave", email), то сопоставления с последним никогда не произойдет, потому что первому из шаблонов соответствует любой экземпляр класса User.</p><p>Если же вам не нужны поля класса, а нужно просто соответствие по типу, используйте шаблон вида valName: TypeName.</p><p>Кроме того, существует шаблон «_», которому удовлетворяет любой объект.</p><p>Результат сопоставления с образцом — значение, возвращённое из value, приведенное к общему надтипу всех блоков value1, … valueN.</p><p>Если ни одному из шаблонов значение не соответствует, выбрасывается исключение PatternMatchingException.</p><p>Приведем пример, объединяющий написанное выше:</p><p>На самом деле, сопоставление с образцом можно применять не только на кейс-классах. На всех типах-примитивах (числа, строки, символы, …), и типов, для которых определён метод-экстрактор. Например, вы можете сопоставлять строку с регулярным выражением. Просто создайте несколько регулярных выражений и применяйте сопоставление с шаблоном:</p><p>Такие действия можно осуществлять, определяя метод unapply и объект-экстрактор. Подробнее об этом можно прочитать в <a href="https://docs.scala-lang.org/tour/extractor-objects.html">документации</a>. Экстрактор имеет ещё одно полезное применение: с помощью него можно распаковывать кейс-классы и другие классы, имеющие экстрактор, используя точно такой же синтаксис, как вид шаблона в сопоставлении с образцом:</p><p>Хотя сопоставление с шаблоном имеет больше возможностей, о которых вы можете почитать в <a href="https://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html">документации</a>, здесь мы ограничимся только этими.</p><h2>Сахар, сахар и ещё раз сахар</h2><p>В этой части мы опишем дополнительный синтаксический сахар, относящийся к функциям.</p><p>Многие функции стандартной библиотеки Scala принимают в качестве параметра другие функции. Например, у списка (List) есть функция map, которая совершает преобразование над каждым его элементом, сигнатура которой упрощенно выглядит так:</p><p>def map[B](f: A=&gt;B): List[B]</p><p>Допустим, у нас есть список целых чисел и нам нужно прибавить к каждому из элементов единицу. Мы могли бы сделать это при помощи лямбда-выражения:</p><p>И получили бы список:</p><p>List(2, 3, 4, 5, 6)</p><p>Но Scala для подобных случаев имеет более лаконичный синтаксис:</p><p>list.map {_ + 1}</p><p>Здесь на место подчёркивания будет подставлен элемент списка. Сокращение в виде подчёркивания может использоваться в самых неожиданных местах, но общая идея его использования — исключение имени переменной там, где можно восстановить однозначным образом манипуляцию над данными, которую вы хотите описать. Например, там, где требуется функция типа (Int, Int) =&gt; Int, вы можете передать суммирование при помощи синтаксиса _ + _. Однако, не всюду такой сахар будет работать, и тогда нужно будет пользоваться обычными лямбда-выражениями. Лямбда-выражения хорошо работают, когда у функции аргументы не запакованы ни в какие обёртки. Если же мы рассмотрим функцию, принимающую функцию из кортежа целых чисел в целое:</p><p>def f(g: Tuple[Int, Int] =&gt; Int) = ???</p><p>То мы не сможем сделать вызов при помощи удобного синтаксиса:</p><p>f { (i1, i2) =&gt; i1 + i2 } // ошибка компиляции</p><p>Функция должна переводить из одного параметра кортежа в целое:</p><p>f { t =&gt; t._1 + t._2 } // верно</p><p>Но такой синтаксис недостаточно выразителен, поэтому для этих целей существует некоторого вида сопоставление с шаблоном. Вообще, синтаксис case &lt;шаблон&gt; if =&gt; value задает частично определённую функцию (partial function). Частично определённая функция, действующая из некоторого набора данных A в набор данных B, отличается от обычной функции тем, что она может быть определена не для всех возможных значений из A. Например, функция извлечения арифметического квадратного корня является частично определённой для всех вещественных чисел от 0 до +∞. Компилятор Scala умеет определять, является ли частичная функция обычной. Если же он обнаружит, что в сопоставлении с шаблоном вы описали не все случаи, он выдаст вам предупреждение с примером входных данных, которые могут выбросить PatternMatchingException.</p><p>Следующая порция синтаксического сахара Scala — ассоциативность операторов. Оператором в Scala называется нестатический метод, который принимает один параметр. Существует два типа операторов: правоассоциативные и левоассоциативные, если вызывать их через точку, то различия нет, но зато, при использовании сокращенного синтаксиса без точки, семантика записи a op b меняется. Правоассоциативные операторы можно вызывать подобным образом:</p><p>a1 op1 a2 op2 a3 … opN-1 aN</p><p>Что эквивалентно записи:</p><p>( ... ((a1.op1(a2)).op2(a3)) …).opN-1(aN)</p><p>Что вполне неплохо сокращает количество скобок. К примеру, мы можем последовательно вызывать метод преобразования элементов списка:</p><p>List(1, 2, 3) map { _ + 1 } map { _ * 2 } map { _ - 1 }</p><p>И это будет эквивалентно записи:</p><p>((List(1, 2, 3).map { _ + 1 }).map { _ * 2 }).map { _ - 1 }</p><p>Левоассоциативные операторы можно вызывать похожим способом:</p><p>a1 op1 a2 op2 a3 … opN-1 aN</p><p>Но это будет эквивалентно совсем другой записи:</p><p>(...((aN.opN-1(aN-1)).opN-2(aN-2))...).op1(a1)</p><p>Левоассоциативные операторы отличаются от правоассоциативных наличием символа «:» на конце. Например, вы можете создавать список, вызывая метод пустого списка (Nil) :::</p><p>1 :: 2 :: 3 :: 4 :: 5 :: Nil,</p><p>что будет эквивалентно:</p><p>((((Nil.::(5)).::(4)).::(3)).::(2)).::(1)</p><p>И создаст список:</p><p>List(1, 2, 3, 4, 5)</p><p>Для унарных и некоторых бинарных операторов существует постфиксная нотация, например, когда у вас нет никаких параметров к методу, вы можете написать value methodName вместо value.methodName.</p><p>Так, например, в стандартной библиотеке в пакете scala.concurrent.duration устроены конструкторы промежутков времени. Вы можете написать:</p><p>На самом деле, вы будете вызывать методы:</p><p>Другое дело, что у обычного Int нет методов second, minutes и milis. Эти методы добавлены к нему при помощи специального механизма методов расширения, о которых мы поговорим в следующей части.</p><p>Некоторые разработчики критикуют возможность создания DSL (Domain Specific Language) прямо внутри языка. Один из основных пунктов критики — код становится непонятным и нечитаемым, разобраться в стрелочках и прочих закорючках становится довольно сложно.</p><p>На самом деле эта критика происходит из неправильного использования DSL: разработчики пытаются создавать предметно-специфический язык, чётко не выделив предметную область.</p><p>Для того чтобы строить полезные и понятные DSL, нужно строго определить область, для которой создаётся язык. Например, стандартная библиотека предоставляет DSL для работы с последовательностями. Далее нужно использовать отличимые и интуитивно понятные символы, и если таких нет, не стесняться использовать слова. Например, использовать -~&gt; и ~-&gt; в одном языке — не самая хорошая идея.</p><p>На самом деле, правильно написанные DSL позволяют сильно сокращать количество написанного кода и делать его более идиоматическим.</p><h2>Заключение</h2><p>В этой части мы довольно подробно рассмотрели, то как устроена ООП система языка Scala. В следующей части мы рассмотрим неявные преобразования, неявные параметры и неявные значения, а также важный конструкт функционального программирования — тайп класс. Кроме того, мы заглянем в стандартную библиотеку и посмотрим, как можно делать хорошо известные вещи, но в функциональном стиле.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основы функционального программирования с примерами на Scala — часть 1</title>
      <link>https://tproger.ru/articles/scala-functional-programming-1</link>
      <comments>https://tproger.ru/articles/scala-functional-programming-1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/scala-functional-programming-1</guid>
      <description><![CDATA[<p>Настройка окружения, ввод переменных и функций в Scala, а также преимущества функционального подхода перед привычным императивным кодом и ООП.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/scala-functional-programming-1">Основы функционального программирования с примерами на Scala — часть 1</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Scala]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 14 Sep 2018 16:43:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Вы наверняка слышали о функциональном стиле программирования, о таких языках как Haskell, Erlang, Scala, F#, OCaml. Для многих из вас эти технологии могут показаться модной выдумкой для IT-хипстеров или же очень непонятной вещью, отсутствующей в реальном мире. На самом деле, эти языки программирования, как и функциональный подход в целом, предоставляют программисту свои преимущества (выходящие за рамки лямбда-выражений) для написания реальных программ, .</p><p>Большинство приложений в мире написано в традициях ООП (объектно-ориентированного программирования) и использует императивный код. Несмотря на то, что объектно-ориентированный подход очень популярен и прост для понимания, он не лишён своих недостатков.</p><h2>Недостатки ООП</h2><p>Во-первых, в большинстве языков высокого уровня обработка ошибок реализована с помощью механизма исключений. Идея о том, что выполнение кода нужно прерывать, как только достигнуто некорректное состояние, хороша, но её реализация в виде выброса исключения и перехвата его в другом месте требует от коллектива программистов железной дисциплины. Иначе простая линейная цепочка вычислений превратится в древообразную, а то и в запутанный граф. Функциональная парадигма предоставляет абстракции для простой и прозрачной обработки ошибок.</p><p>Во-вторых, императивный код подобен тому, как человек представляет себе порядок каких-то действий. Такой подход очень хорош для описания последовательных вычислений, однако в современном мире большинство приложений асинхронные и многопоточные, и здесь уследить за взаимодействием императивного кода, выполняющегося во многих потоках, очень сложно. Наверное, каждый программист сталкивался с ситуациями, когда два потока пытаются изменить одно и то же значение, или же когда один поток ждёт другой поток, а тот, в свою очередь, ждёт первый?</p><p>В-третьих, не существует математически строгой, аксиоматизированной и пригодной к повседневному применению теории, описывающей паттерны проектирования и систему типов. Паттерны проектирования — это, конечно, большой шаг в сторону стандартизации способов проектирования, но и они — скорее сборник советов и указаний, нежели строгая теория. Исходя из этого, комбинируя паттерны, вы никогда с полной уверенностью не будете знать, что получите в итоге, если вы не делали этого ранее, что, в свою очередь, ведёт к разного рода «неожиданностям» при разработке.</p><h2>Функциональный подход</h2><p>В функциональном программировании решили подойти с другой стороны. Во-первых, решили отказаться от возможности изменять переменные настолько, насколько это возможно (существуют фундаментальные ограничения, не позволяющие сделать это полностью). Функция в смысле информатики не является функцией в смысле математики: например, функция датчика случайных чисел (на основе физического датчика) не является математической функцией — она не принимает параметров, и потому, с точки зрения математики, должна быть константой, но каждый раз эта функция выдаёт разный результат. Такого в математике не бывает. Если же мы запретим переменным изменяться, то есть уничтожим глобальное изменяемое состояние, то функции станут действительно математическими, и можно будет построить строгую математическую теорию. Такая теория была построена и ныне носит название λ-исчисления. Эта теория описывает типы и полиморфизм.</p><p>Математиками-топологами была построена достаточно абстрактная теория категорий, которую они использовали для сокращения доказательств. Со временем теория категорий нашла своё применение во множестве отраслей математики. Информатики выяснили, что эта теория подходит для описания операций с реальными данными, которые не всегда могут быть корректными. Например, понятие монады можно применить для описания вычислений, которые могут привести к ошибке.</p><p>Монады бывают двух видов. Первый вид — «чистая» монада, она представляет корректные данные. Второй вид — некорректная монада, она нужна для представления некорректных данных. Над этими двумя типами данных определены операции:</p><ol><li>pure — функция создания «чистой» монады.</li><li>flatMap — применение преобразования, которое может привести к некорректному результату. К примеру, операция деления целых чисел будет корректно обрабатывать все данные, кроме деления на 0. В этом случае будет создана некорректная монада и применение к ней операции flatMap её не изменит.</li><li>map — применение к данным операции, которая не может вызвать исключений по своей природе, например, операции сложения.</li></ol><p>Важно понимать, что функциональное программирование занимает нишу в середине между низкоуровневыми операциями и очень высокими уровнями абстракции — например, такими, как уровень сервисов или приложений. Исходя из этого был создан объектно-функциональный стиль, позволяющий использовать императивный код для низкоуровневых функций, чистый функциональный код в середине приложения и паттерны проектирования и прочие ООП-технологии на наивысшем уровне абстракции. Таким образом, функциональный подход лучше применять не для создания замкнутых систем, а скорее для слоёв приложений и описания бизнес-логики. Именно в таком ключе функциональное программирование и применяется на сегодняшний день — построенное над низкоуровневым императивным кодом, внутри объектных абстракций.</p><p>Для того чтобы приводить жизненные примеры и показывать, зачем нужна та или иная функциональная конструкция, начнём с краткого, но довольно исчерпывающего введения в мультипарадигменный язык программирования с сильной поддержкой функционального подхода — Scala.</p><p>В этой статье будут описаны фундаментальные языковые конструкции и некоторые средства сокращения кода.</p><h2>О Scala</h2><p>Изначально язык Scala был разработан Мартином Одерски (который также работал над Generics в Java и над компилятором Javac) в EPFL. Первая версия языка была выпущена в мир в 2004 году на платформе Java. Нынешняя версия языка (2-я) имеет версии для платформ JavaScript (Scala-js) и под LLVM (Scala Native).</p><p>Изначально многие решения, реализованные в этом языке, были направлены на устранение недостатков языка Java, таких как слабая типобезопасность и отсутствие синтаксического сахара. Притом новый язык сохранил возможность практически бесшовной интеграции с Java — вы можете использовать все библиотеки, написанные на Java, без падения производительности.</p><p>Язык был оценен по достоинству крупными корпорациями: например, он активно используется в Twitter, LinkedIn, Netflix, Sony и других.</p><h2>Установка IDE и SBT</h2><p>Чтобы использовать Scala под платформу Java, вам понадобятся установленные JRE и JDK. Для знакомства с языком разумно использовать IDE <a href="https://www.jetbrains.com/idea/">IntelliJ IDEA Community Edition</a> со специальным плагином Scala Plugin (доступен прямо при установке IDE) и систему построения проектов SBT (есть под большинство ОС, скачивается при создании проекта).</p><p>Примеры из статьи можно запускать, используя онлайн-компилятор <a href="https://scastie.scala-lang.org/">Scalastie</a>.</p><p>Для того чтобы создать ваш первый проект, нужно сделать следующее:</p><p>new project → scala → sbt</p><figure><img src="https://media.tproger.ru/uploads/2018/09/image2.png" alt="" /></figure><p>В меню создания проекта нужно выбрать версию SBT и Scala, последними на момент написания статьи являются 1.2.1 и 2.12.6 соответственно.</p><p>После нажатия кнопки создания проекта IDEA отдаст указание о создании проекта SBT (это можно понять по надписи dump project structure from SBT), поэтому структура папок проекта появится не сразу.</p><p>После формирования проекта вы можете увидеть подобную структуру папок:</p><figure><img src="https://media.tproger.ru/uploads/2018/09/image1.png" alt="" /></figure><p>Нас в основном будут интересовать папка src и её подпапки main/scala и test/scala, а также файл build.sbt. Файл build.sbt описывает всю структуру проекта и используется для определения модулей, подмодулей, управления зависимостями и версиями библиотек. Сюда же стоит дописывать импорты сторонних библиотек.</p><p>В папке main/scala нужно создать new scala class и выбрать там object (о том, что это такое и какую роль он играет в Scala, будет сказано ниже). Обратите внимание, что имя обджекта должно совпадать с именем файла, в котором он расположен, иначе вы можете столкнуться с ошибками при попытке запустить вашу первую программу.</p><p>После создания обджекта вы увидите следующий код:</p><p>В теле обджекта нужно создать точку входа в программу, а именно метод main() (в Scala методы объявляются ключевым словом def), автодополнение IDEA предложит вам сгенерировать метод как только вы начнёте набирать def main. В итоге должен получиться такой код:</p><p>Если же вам не нужны параметры командной строки args, вы можете сократить свой код до:</p><p>Отметим несколько отличительных особенностей: во-первых, в списке параметров сначала идут имена параметров, а потом их типы после двоеточия, во-вторых, тип возвращаемого значения указывается через двоеточие после списка параметров (в Scala все функции всегда возвращают значение, но когда необходимости в этом нет, используется тип Unit — это аналог ключевого слова void, указывающий на отсутствие какого-либо возвращаемого значения).</p><p>Добавив строчку println("Hello world"), вы сможете запустить эту программу, нажав на зелёный треугольник напротив метода main. Если всё сделано правильно, в консоли должна появиться строка «Hello world».</p><p>Мы настоятельно рекомендуем всем любителям чистых текстовых редакторов на этапе знакомства с языком пользоваться IDEA (пусть это и противоречит философии Unix). Scala содержит большое количество достаточно хитрых конструкций, поддержку которых редко когда можно встретить в редакторе. Кроме того, у IDEA есть много полезных языко-специфических функций. С их помощью IDEA может спасти вас от некоторых глупых ошибок ещё во время написания кода.</p><h2>Переменные</h2><p>В языке Scala есть 2 типа переменных — val и var.</p><p>val — это неизменяемая переменная, ей можно присвоить значение только при инициализации. Синтаксис введения постоянной выглядит так:</p><p>val valueName: TypeName = value</p><p>Опять же, как и у параметров функции, имя постоянной находится сначала, а имя типа — после двоеточия, за именем постоянной. В Scala существует продвинутая система типов, поэтому в большинстве случаев вы можете опускать имя типа, компилятор выведет его за вас (IDEA тоже это умеет, поставьте курсор на имя переменной и нажмите Ctrl+Q).</p><p>Для того чтобы объявить переменную, вам нужно использовать ключевое слово var:</p><p>var valueName: TypeName = value</p><p>Как и в случае постоянной, тип в большинстве случаев можно опускать.</p><p>Переменные можно объединять в кортежи при помощи удобного синтаксиса запаковки в кортеж:</p><p>val tuple: (Type1, Type2, ... , TypeN) = (val1, val2, … , valN )</p><p>И распаковки из кортежа:</p><p>val (val1: Type1, … valN: TypeN) = tuple</p><p>Здесь типы тоже можно опускать, сокращая код до:</p><p>val tuple = (val1, val2, … , valN )val (val1, … val2) = tuple</p><h2>Методы</h2><p>Для определения методов используется ключевое слово def. Синтаксис для определения метода выглядит так:</p><p>В определении вы наверняка не увидели слово return. Это потому, что последнее значение в функции является возвращаемым. То же самое касается и оператора if: вопреки принятой в императивных языках логике, оператор if (condition) value_if_true else value_if_false имеет возвращаемое значение, имеющее тип, общий над value_if_true/value_if_false и значение, в зависимости от условия равное value_if_true/value_if_false.</p><p>В функциях не обошлось без синтаксического сахара. Если функция не принимает параметров, то вы можете не писать скобки. Кроме того, можно не указывать тип возвращаемого значения. Как и в случае с переменными, тип будет выведен (в IDEA этот тип будет напечатан фантомным текстом). Ещё одна «сахарная» особенность — если тело метода достаточно короткое и содержит всего лишь одну инструкцию, вы можете не писать фигурных скобок. Например, такой код будет абсолютно корректен:</p><p>def five = 5</p><p>Хотелось бы отметить, что функция может быть значением и при этом будет иметь функциональный тип, который записывается как SourceType =&gt; ResultType.</p><p>К примеру, функция, которая увеличивает число на 1 (здесь num + 1 — возвращаемое значение, а Int — его тип):</p><p>def inc (num: Int) = num +1</p><p>будет иметь тип Int =&gt; Int.</p><p>Тогда мы можем ввести постоянные, значениями которых будут наши функции:</p><p>val incFunc: Int=&gt;Int = inc</p><p>В чём же разница между val и def? Дело в том, что значение val вычисляется однажды и дальше используется при каждом упоминании, а значение типа def вычисляется каждый раз при упоминании. Проверить это можно следующим фрагментом кода:</p><p>Первые две строчки вывода будут совпадать, а вторые две — различаться.</p><p>Если же у функции много входных параметров, тип можно записать так:</p><p>(Type1, Type2, … TypeN) =&gt; ResultType</p><p>К примеру, функция сложения двух чисел:</p><p>def add(a: Int, b: Int) = a+b</p><p>будет иметь тип: (Int, Int) =&gt; Int.</p><p>Если же нам нужно вернуть несколько значений из функции, мы можем воспользоваться упаковкой и распаковкой в кортеж.</p><p>Для функций существует синтаксический сахар, позволяющий создать функцию, не вводя для неё отдельного метода. Он может быть вам знаком, потому как присутствует в других языках:</p><p>(argument_list) =&gt; value</p><p>value вполне может быть блоком, в котором вы можете вводить дополнительные переменные и совершать некоторые действия. Примеры:</p><p>Поскольку для функций существует тип, мы можем создавать функции от функций, то есть функции высшего порядка. Для этого достаточно просто дописать параметру функциональный тип. К примеру, можно написать функцию, которая к двум числам применяет заданное преобразование:</p><p>def transform (a: Int, b: Int, f: (int, Int) =&gt; Int): Int = f(a,b)</p><p>Есть ещё один нюанс, связанный с функциями. Существует 2 семантики передачи параметров: call by name и call by value. Call by value используется по умолчанию: значение сначала вычисляется, а потом передаётся в функцию. Call by name, наоборот, сначала передаётся в функцию, а потом вычисляется в каждом месте упоминания. Для указания этой семантики перед типом ставится знак =&gt;. Продемонстрировать это можно следующим примером:</p><p>Вы должны получить следующий вывод: 1 1 1 2 3 4. Таким образом, если вы хотите передать в функцию генератор случайных чисел, вы должны использовать семантику callByName.</p><h2>Заключение</h2><p>Мы познакомились с основами языка программирования Scala: рассмотрели, как настроить окружение, как вводить переменные, функции. Мы также рассмотрели два вида семантики методов — callByName и callByValue.</p><p>В следующей статье мы рассмотрим объектную модель языка Scala и некоторые особенности, связанные с синтаксическим сахаром.</p>]]></content:encoded>
    </item>
    <item>
      <title>Благотворительный проект Humble Bundle выставил на продажу ограниченную серию книг по функциональному программированию</title>
      <link>https://tproger.ru/news/humble-bundle-functional-programming-books</link>
      <comments>https://tproger.ru/news/humble-bundle-functional-programming-books?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Светлана Хачатурян]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/humble-bundle-functional-programming-books</guid>
      <description><![CDATA[<p>Набор O’Reilly включает 15 книг о JavaScript, Haskell, Scala, Clojure, Elixir, Erlang, Rust и основах функционального программирования на английском языке.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/humble-bundle-functional-programming-books">Благотворительный проект Humble Bundle выставил на продажу ограниченную серию книг по функциональному программированию</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 13 Feb 2018 21:23:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Humble Bundle — это благотворительная платформа, предоставляющая разнообразный контент (от игр до ПО и книг) за символическую плату. На днях создатели <a href="https://www.humblebundle.com/books/functional-programming-books">открыли</a> новый лот, содержащий 15 электронных книг по функциональному программированию от O’Reilly общей стоимостью в 515 $.</p><h3>Особенности</h3><p>Пользователям на выбор предоставляется три варианта книжных наборов:</p><ul><li>5 книг за 1 $ и выше:<a href="https://media.tproger.ru/uploads/2018/02/Snimok-jekrana-2018-02-13-v-23.18.22.jpg"></a></li><li>еще 5 книг за 8 $ и выше:<a href="https://media.tproger.ru/uploads/2018/02/Snimok-jekrana-2018-02-13-v-23.51.15.png"></a></li><li>дополнительные 5 книг за 15 $ и выше: <a href="https://media.tproger.ru/uploads/2018/02/Snimok-jekrana-2018-02-13-v-23.51.30.jpg"></a></li></ul><p>Пособия включают в себя материалы по языкам JavaScript, Haskell, Scala, Clojure, Elixir, Erlang и Rust, а также общие учебники по основам функционального программирования. Книги предоставляются участникам благотворительной акции в формате PDF, ePUB или MOBI. Материалы доступны только на английском языке. Более подробно с представленными книгами можно ознакомиться в источнике. Предложение действует до 26 февраля.</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/video/functional-programming</link>
      <comments>https://tproger.ru/video/functional-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дмитрий Баранчук]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/functional-programming</guid>
      <description><![CDATA[<p>Видеокурс «Функциональное программирование»от НОУ «ИНТУИТ» , записанный в 2013 году.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/functional-programming">Курс «Функциональное программирование»</a>»</p>]]></description>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 13 Jun 2017 18:39:41 GMT</pubDate>
      <content:encoded><![CDATA[<p>Русскоязычный видеокурс от НОУ «ИНТУИТ», записанный в 2013 году. Цель курса — познакомить слушателей с парадигмой функционального программирования, в которой решение задач сводится к построению функций на основе принципов функциональной абстракции и аппликации. Лектор курса — Дмитрий Сошников.</p><p>В курсе подробно разбираются теоретические основы функционального программирования: лямбда-исчисление, комбинаторная логика, вопросы вычислимости. Рассматриваются вопросы использования функциональных языков для построения компиляторов, грамматического разбора и т.д. Также на примере функционального подхода дается представление о теоретических разделах компьютерных наук таких, как семантика языков программирования и доказательство программ.</p><p>Смотрите также другие наши материалы по функциональному программированию.</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональное программирование для Android-разработчика. Часть первая</title>
      <link>https://tproger.ru/translations/functional-android-1</link>
      <comments>https://tproger.ru/translations/functional-android-1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дмитрий Юрченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-android-1</guid>
      <description><![CDATA[<p>Первая часть цикла знакомит с основами функционального программирования, чистыми функциями, отсутствием изменяемых данных и побочных эффектов в Android-коде.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-android-1">Функциональное программирование для Android-разработчика. Часть первая</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Kotlin]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 27 May 2017 21:55:01 GMT</pubDate>
      <content:encoded><![CDATA[<p>В этой серии статей мы изучим основы функционального программирования, его концепции и методы, которые будут полезны для Android-разработки.</p><p>В этой части мы рассмотрим пять концепций:</p><ul><li><a href="https://tproger.ru/translations/functional-android-1/#purefuctions">чистые функции;</a></li><li><a href="https://tproger.ru/translations/functional-android-1/#sideeffects">побочные эффекты;</a></li><li><a href="https://tproger.ru/translations/functional-android-1/#Order">порядок;</a></li><li><a href="https://tproger.ru/translations/functional-android-1/#Immutability">неизменяемость;</a></li><li><a href="https://tproger.ru/translations/functional-android-1/#Concurency">конкурентность.</a></li></ul><h2>Что такое функциональное программирование и почему мы должны его использовать?</h2><p>Прим. перев.  Советуем посмотреть наше <a href="https://tproger.ru/translations/functional-js-1/">руководство</a> по функциональному программированию c примерами на JavaScript. Также у нас на сайте есть <a href="https://tproger.ru/translations/functional-sharp-1/">цикл</a> статей о функциональном C#.</p><p>Термин «функциональное программирование» является объединением ряда концепций программирования. Это стиль программирования, который рассматривает программу как последовательность математических функций и предполагает отсутствие изменяемых данных и побочных эффектов.</p><p>Основы ФП, которые стоит выделить:</p><ul><li>используется декларативный код;</li><li>ясность — код должен быть как можно более очевидным. В частности, побочные эффекты должны быть изолированы, а поток данных и обработка ошибок — описаны явно;</li><li>параллелизм — функциональный код по умолчанию конкурентен из-за концепции, известной как функциональная чистота;</li><li>функции высшего порядка;</li><li>неизменяемость — переменные не подлежат изменению после их инициализации.</li></ul><h2>Чистые функции</h2><p>Функция считается чистой, если её выходные данные зависят только от входных и она не имеет побочных эффектов (поговорим о побочных эффектах ниже).</p><p>Рассмотрим простую функцию, которая складывает два числа. Она читает одно число из файла, а другое принимает в качестве параметра:</p><p>Java</p><p>Kotlin</p><p>Выходные данные этой функции зависят не только от входных. В зависимости от того, что возвращает readNumFromFile(), выходные значения для одного и того же x могут быть разными. Эта функция называется функцией с побочным эффектом. Превратим ее в чистую функцию.</p><p>Java</p><p>Kotlin</p><p>Теперь выходные значения функции зависят только от входных. Для заданных x и y функция всегда будет возвращать один и тот же результат. Теперь эта функция чистая. Математические функции работают так же: их выходные значения зависят только от входных. Поэтому функциональное программирование гораздо ближе к математике, чем к привычному стилю программирования.</p><h2>Побочные эффекты</h2><p>Рассмотрим эту концепцию с тем же примером. Модифицируем функцию, чтобы мы могли записать результат в файл:</p><p>Java</p><p>Kotlin</p><p>Функция записывает результат вычисления в файл, т.е. меняет состояние «внешнего мира». Такую функцию уже не называют чистой, теперь у неё есть побочный эффект.</p><p>Любая функция, которая изменяет переменную, удаляет что-то, записывает в файл или в БД, имеет побочный эффект и не используется в ФП.</p><p>Предположим, что вы пишете фрагмент кода, который зависит от кэша. Теперь результат выполнения вашего кода зависит от того, что находилось в кэше и валидны ли данные в нём. Вы не сможете понять, что делает программа, если не понимаете все возможные состояния кэша. Добавим сюда другие вещи, от которых зависит приложение: сеть, база данных, файлы, ввод пользователя. Становится очень сложно узнать, что именно происходит. Значит ли это, что в ФП не используются базы данных, кэши и т.д.? Конечно же, нет.</p><p>Идеология ФП заключается не в том, чтобы отказаться от побочных эффектов, а в том, чтобы изолировать их. Мы поговорим об этом подробно, когда будем исследовать функциональную архитектуру наших приложений.</p><h2>Порядок</h2><p>Если у нас есть много чистых функций, которые не имеют побочных эффектов, то порядок, в котором они выполняются, становится неактуальным.</p><p>Предположим, у нас есть функция, которая вызывает 3 чистых функции:</p><p>Java</p><p>Kotlin</p><p>Мы знаем, что эти функции не зависят друг от друга и что они ничего не изменят в системе. Это делает порядок, в котором они выполняются, полностью взаимозаменяемым. Обратите внимание, что если бы doThing2() была результатом doThing1(), то функции должны были бы выполняться по порядку, но doThing3() могла бы быть выполнена перед doThing1().</p><p>Что нам это даёт? <a href="https://tproger.ru/translations/programming-concepts-concurrency/">Конкурентность</a>, вот что! Мы можем запустить эти функции на 3 отдельных ядрах процессора, ни о чём не беспокоясь.</p><p>В основном, компиляторы функциональных языков, таких как Haskell, могут проанализировать ваш код и сказать, является ли он параллельным. Теоретически эти компиляторы могут распараллелить ваш код автоматически (но на практике пока такое не реализовано).</p><h2>Неизменяемость</h2><p>Идея неизменяемости заключается в том, что созданное значение никогда не может быть изменено.</p><p>Предположим, что у нас есть класс Car:</p><h3>Java</h3><h3>Kotlin</h3><p>Поскольку у класса есть метод в Java и он является изменяемым в Kotlin, я могу изменить имя машины после того, как я его создал:</p><h3>Java</h3><p>Kotlin</p><p>Этот класс не является неизменяемым. Его можно изменить после создания. Давайте сделаем его неизменяемым. Чтобы сделать это на Java, мы должны:</p><ul><li>создать переменную final;</li><li>удалить метод;</li><li>создать класс final так, чтобы другой класс не смог расширить и изменить его.</li></ul><h3>Java</h3><p>В Kotlin нам просто нужно сделать название класса неизменяемым.</p><h3>Kotlin</h3><p>Теперь, если кому-то нужно создать новый автомобиль, им нужно инициализировать новый объект. Никто не сможет изменить наш автомобиль, когда он будет создан. Этот класс теперь неизменяемый.</p><p>Но что насчет метода getName() в Java? В нем строки по умолчанию неизменяемы. Даже если кто-то получил ссылку на нашу строку и попытается ее изменить, то они получат копию этой строки, а исходная строка осталась бы неизменной. Но что относительно вещей, которые не являются неизменяемыми? Возможно, список? Давайте модифицируем класс Car, чтобы иметь список людей, которые им управляют.</p><h3>Java</h3><p>В этом случае кто-то может использовать метод getListOfDrivers(), чтобы получить ссылку на наш внутренний список и изменить его, что делает наш класс изменяемым.</p><p>Чтобы сделать его неизменяемым, мы должны передать глубокую копию списка, чтобы новый список мог быть безопасно изменен вызывающим. Глубокая копия означает, что мы копируем все зависимые данные рекурсивно. Например, если список был списком объектов «Водитель» вместо простых строк, нам также пришлось бы копировать каждый из объектов «Водитель». В противном случае мы создадим новый список со ссылками на исходные объекты, которые могут быть изменены. В нашем классе список состоит из неизменяемых строк, поэтому мы сделаем глубокую копию следующим образом:</p><h3>Java</h3><p>Теперь наш класс действительно неизменяем.</p><p>В Kotlin мы можем просто объявить список неизменяемым в определении класса, а затем безопасно использовать его (если вы, конечно, не вызываете его из Javа).</p><h3>Kotlin</h3><h2>Конкурентность</h2><p>Как мы уже говорили выше, чистые функции позволяют нам упростить конкурентность. Если объект неизменяем, то его очень просто использовать в чистых функциях, поскольку вы не можете его изменить и тем самым вызвать побочные эффекты.</p><p>Давайте посмотрим на пример. Предположим, что мы добавили метод getNoOfDrivers() в наш класс Car в Java. Мы делаем его изменяемым как в Kotlin, так и в Java, позволяя пользователю изменять количество переменных водителей следующим образом:</p><h3>Java</h3><h3>Kotlin</h3><p>Разделим экземпляр класса Car через 2 потока: Thread_1 и Thread_2. Thread_1 хочет сделать некоторые вычисления на основе количества водителей. Он вызывает метод getNoOfDrivers() в Java или обращается к свойству noOfDrivers в Kotlin. Тем временем Thread_2 изменяет переменную noOfDrivers. Thread_1 не знает об этом изменении и продолжает свои расчеты. Эти вычисления будут неправильными, поскольку состояние мира было изменено без Thread_2 и Thread_1.</p><p>Следующая диаграмма иллюстрирует эту проблему:</p><figure><img src="https://media.tproger.ru/uploads/2017/06/1-1.png" alt="" /></figure><p>Эта проблема называется Read-Modify-Write. Традиционный способ решить ее — использовать блокировки и <a href="https://ru.wikipedia.org/wiki/Мьютекс">мьютексы</a>, чтобы только один поток мог работать с данными. В нашем случае Thread_1 будет удерживать блокировку до тех пор, пока не завершит расчет.</p><p>Этот тип управления ресурсами трудно сделать безопасным, и он приводит к ошибкам конкурентности, которые трудно анализировать.</p><p>Как это исправить? Давайте сделаем класс CAR снова неизменяемым:</p><h3>Java</h3><h3>Kotlin</h3><p>Теперь Thread_1 может выполнять вычисления без проблем, так как гарантировано, что Thread_2 не сможет изменить класс Car. Если Thread_2 хочет изменить класс, тогда он создаст собственную копию. Никаких блокировок не потребуется.</p><figure><img src="https://media.tproger.ru/uploads/2017/06/2.png" alt="" /></figure><p>Неизменяемость гарантирует, что данные, которые не должны быть изменены, не будут изменены.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: функциональное программирование</title>
      <link>https://tproger.ru/translations/functional-programming-concepts</link>
      <comments>https://tproger.ru/translations/functional-programming-concepts?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-programming-concepts</guid>
      <description><![CDATA[<p>Знакомство с функциональной парадигмой для тех, кто начинал с ООП: чем она отличается от процедурного и прототипно-ориентированного подходов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-programming-concepts">Основные принципы программирования: функциональное программирование</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 23 Jan 2017 10:43:04 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>Если вы такой же разработчик, как и я, то наверняка сперва изучали парадигму ООП. Первым вашим яыком были Java или C++ — или, если вам повезло, Ruby, Python или C# — поэтому вы наверняка знаете, что такое классы, объекты, экземпляры и т.д. В чём вы точно не особо разбираетесь, так это в основах той странной парадигмы, называющейся функциональным программированием, которая существенно отличается не только от ООП, но и от процедурного, прототипно-ориентированного и других видов программирования.</p><p>Функциональное программирование становится популярным — и на то есть причины. Сама парадигма не нова: <a href="https://www.haskell.org/">Haskell</a>, пожалуй, является самым функциональным языком, а возник он в 90-ых. Такие языки, как Erlang, Scala, Clojure также попадают под определение функциональных. Одним из основных преимуществ функционального программирования является возможность написания программ, работающих конкурентно (если вы уже забыли, что это — освежите память прочтением <a href="https://tproger.ru/translations/programming-concepts-concurrency/">статьи о конкурентности</a>), причём без ошибок — то есть взаимные блокировки и потокобезопасность вас не побеспокоят.</p><p>У функционального программирования есть много преимуществ, но возможность максимального использования ресурсов процессора благодаря конкурентному поведению — это его главный плюс. Ниже мы рассмотрим основные принципы функционального программирования.</p><p>Вступление: Все эти принципы не обязательны (многие языки следуют им не полностью). Все они теоретические и нужны для наиболее точного определения функциональной парадигмы.</p><h3>1. Все функции — чистые</h3><p>Это правило безусловно является основным в функциональном программировании. Все функции являются чистыми, если они удовлетворяют двум условиям:</p><ol><li>Функция, вызываемая от одних и тех же аргументов, всегда возвращает одинаковое значение.</li><li>Во время выполнения функции не возникают <a href="https://ru.wikipedia.org/wiki/Побочный_эффект_(программирование)">побочные эффекты</a>.</li></ol><p>Первое правило понятно — если я вызываю функцию sum(2, 3), то ожидаю, что результат всегда будет равен 5. Как только вы вызываете функцию rand(), или обращаетесь к переменной, не определённой в функции, чистота функции нарушается, а это в функциональном программировании недопустимо.</p><p>Второе правило — никаких побочных эффектов — является более широким по своей природе. Побочный эффект — это изменение чего-то отличного от функции, которая исполняется в текущий момент. Изменение переменной вне функции, вывод в консоль, вызов исключения, чтение данных из файла — всё это примеры побочных эффектов, которые лишают функцию чистоты. Может показаться, что это серьёзное ограничение, но подумайте ещё раз. Если вы уверены, что вызов функции не изменит ничего “снаружи”, то вы можете использовать эту функцию в любом сценарии. Это открывает дорогу конкурентному программированию и многопоточным приложениям.</p><h3>2. Все функции — первого класса и высшего порядка</h3><p>Эта концепция — не особенность ФП (она используется в Javascript, PHP и других языках) — но его обязательное требование. На самом деле, на Википедии есть целая статья, посвящённая <a href="https://ru.wikipedia.org/wiki/Функции_первого_класса">функциям первого класса</a>. Для того, чтобы функция была первоклассной, у неё должна быть возможность быть объявленной в виде переменной. Это позволяет управлять функцией как обычным типом данных и в то же время исполнять её.</p><p><a href="https://ru.wikipedia.org/wiki/Функция_высшего_порядка">Функции высшего порядка</a> же определяются как функции, принимающие другую функцию как аргумент или возвращающие функцию. Типичными примерами таких функций являются <a href="https://en.wikipedia.org/wiki/Map_(higher-order_function)">map</a> и <a href="https://en.wikipedia.org/wiki/Filter_(higher-order_function)">filter</a>.</p><h3>3. Переменные неизменяемы</h3><p>Тут всё просто. В функциональном программировании вы не можете изменить переменную после её инициализации. Вы можете создавать новые, но не можете изменять существующие — и благодаря этому вы можете быть уверены, что никакая переменная не изменится.</p><h3>4. Относительная прозрачность функций</h3><p>Сложно дать корректное определение <a href="https://en.wikipedia.org/wiki/Referential_transparency">относительной прозрачности</a>. Самым точным я считаю такое: если вы можете заменить вызов функции на возвращаемое значение, и состояние при этом не изменится, то функция относительно прозрачна. Это, быть может, очевидно, но я приведу пример.</p><p>Пусть у нас есть Java-функция, которая складывает 3 и 5:</p><p>Очевидно, что любой вызов этой функции можно заменить на 8 — значит, функция относительно прозрачна. Вот пример непрозрачной функции:</p><p>Эта функция ничего не возвращает, но печатает текст, и при замене вызова функции на ничто состояние консоли будет другим — значит, функция не является относительно прозрачной.</p><h3>5. Функциональное программирование основано на лямбда-исчислении</h3><p>Функциональное программирование сильно опирается на математическую систему, называющуюся <a href="https://ru.wikipedia.org/wiki/Лямбда-исчисление">лямбда-исчислением</a>. Я не математик, поэтому я не буду углубляться в детали — но я хочу обратить внимание на два ключевых принципа лямбда-исчисления, которые формируют самое понятие функционального программирования:</p><ol><li>В лямбда-исчислении все функции могут быть анонимными, поскольку единственная значимая часть заголовка функции — это список аргументов.</li><li>При вызове все функции проходят процесс <a href="https://ru.wikipedia.org/wiki/Каррирование">каррирования</a>. Он заключается в следующем: если вызывается функция с несколькими аргументами, то сперва она будет выполнена лишь с первым аргументом и вернёт новую функцию, содержащую на 1 аргумент меньше, которая будет немедленно вызвана. Этот процесс рекурсивен и продолжается до тех пор, пока не будут применены все аргументы, возвращая финальный результат. Поскольку функции являются чистыми, это работает.</li></ol><p>Как я уже говорил, лямбда-исчисление на этом не заканчивается — но мы рассмотрели лишь ключевые аспекты, связанные с ФП. Теперь, в разговоре о функциональном программировании вы сможете блеснуть словечком “лямбда-исчисление”, и все подумают, что вы шарите 🙂</p><h3>Заключение</h3><p>Функциональное программирование серьёзно напрягает мозги — но это очень мощный подход, и я считаю, что его популярность будет только расти.</p><p>Если вы хотите узнать о функциональном программировании побольше, то советуем вам ознакомиться с примерами использования принципов ФП в JavaScript (<a href="https://tproger.ru/translations/functional-js-1/">часть 1</a>, <a href="https://tproger.ru/translations/functional-js-2/">часть 2</a>), а также с <a href="https://tproger.ru/translations/functional-c-immutability/">циклом статей</a>, посвящённым функциональному C#.</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональное программирование с примерами на JavaScript. Часть вторая. Аппликативные функторы, curryN и валидации</title>
      <link>https://tproger.ru/translations/functional-js-2</link>
      <comments>https://tproger.ru/translations/functional-js-2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Антон Корольков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-js-2</guid>
      <description><![CDATA[<p>Вторая часть обзора инструментов ФП на JavaScript: аппликативные функторы, функция curryN и валидации для работы со значениями, которые могут быть null.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-js-2">Функциональное программирование с примерами на JavaScript. Часть вторая. Аппликативные функторы, curryN и валидации</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 30 Dec 2016 00:33:14 GMT</pubDate>
      <content:encoded><![CDATA[<p>В <a href="https://tproger.ru/translations/functional-js-1/">прошлой части</a> мы рассмотрели основные инструменты функционального программирования с примерами на JavaScript: монады, функторы, каррирование. В этой статье мы закончим обзор принципов и инструментов, которые помогут вам построить поистине чистое приложение в функциональном стиле.</p><h3>Пример 3.  Задание значений объектам, которые могут равняться Null</h3><p>Используемые концепты ФП: Аппликативные функторы.</p><p>Сценарий использования: Предположим, что мы хотим предоставить скидку пользователю, если пользователь залогинен и у нас есть действующее предложение (т.е. существует скидка).</p><figure><img src="https://media.tproger.ru/uploads/2016/12/1-qqkZuYVXrxIsyuJ5xTa_Yw-1024x556.png" alt="" /></figure><p>Для этой задачи мы будем использовать метод applyDiscount, представленный ниже. Этот метод может выбрасывать ошибки типа null в случае, если пользователь или скидка равняется null.</p><p>Давайте посмотрим, как мы можем решить эту задачу, используя аппликативные функторы.</p><h4>Аппликативный функтор</h4><p>Любой класс, у которого есть метод ap и который имплементирует спецификацию Applicative, называется аппликативным функтором. Аппликативные функторы используются в функциях, которые работают с возможными null-значениями в правой и левой части присваивания.</p><p>Оказывается, Maybe-монады также реализуют метод ap, и, следовательно, являются аппликативными функторами. Таким образом, мы можем использовать Maybe-монады для решения этой задачи.</p><p>Давайте взглянем на то, как заставить функцию applyDiscount работать, используя Maybe-монады в качестве аппликативных функторов.</p><p>Шаг 1: Обернем потенциальные null-объекты в Maybe-монады.</p><p>Шаг 2: Перепишем функцию так, чтобы она могла принимать один параметр за раз (каррируем её).</p><p>Шаг 3: Передадим первый агрумент (maybeUser) в метод applyDiscount, используя map.</p><p>Шаг 4: Используем maybeApplyDiscountFunc.</p><p>Значение maybeApplyDiscountFunc может быть:</p><ol><li>функцией, обернутой в Maybe, если пользователь существует.</li><li>Nothing, если пользователь равен null.</li></ol><p>Если пользователь равен null, то при передаче второго аргумента в функцию, ничего не произойдет. Не выбросятся также и ошибки, связанные с нулевыми значениями.</p><p>В случае, когда пользователь существует, мы можем передать второй аргумент, используя map, чтобы запустить функцию:</p><p>Мы столкнулись с проблемой: map не знает, как запустить функцию, когда она обернута в Maybe-монаду.</p><p>В этом случае нам нужен другой метод, который умеет работать с обернутыми функциями. На помощь нам приходит метод ap.</p><p>Шаг 5: Используем функцию ap. Этот метод принимает монаду Maybe и выполняет функцию, хранящуюся внутри.</p><p>Применим метод ap:</p><p>Подведем итог: если у вас есть функция, которая работает с несколькими переменными, значения которых может быть null, вы должны сначала каррировать её, а затем обернуть в Maybe. Кроме этого, поместите все параметры в Maybe и используйте ap, чтобы запустить функцию.</p><h4>Множественное каррирование</h4><p>Мы уже познакомились с каррированием в прошлой части цикла. Оно позволяет передавать значения в функцию, которая принимает несколько аргументов, по одному.</p><p>Но что если у нас будет функция, которая может суммировать не два, а несколько аргументов?</p><p>Мы все еще можем каррировать эту функцию, ограничивая число аргументов, используя curryN:</p><p>Использование curryN для ожидания определенного количества вызовов функции.</p><p>Предположим, что мы хотим реализовать функцию, которая выводит лог только после 3х её вызовов.</p><p>Мы можем написать эту функцию в функциональном стиле, используя curryN:</p><h3>Пример 4.  Сбор и отображение нескольких ошибок</h3><p>Освещенные темы: Валидации (Валидационный функтор, Валидационный аппликативный функтор, Валидационная монада)</p><p>Валидации подобны монадам Either и используются в работе с композицией функций, выбрасывающих ошибки. Но, в отличие от Either, в котором для композиции используется метод chain, в валидационных монадах мы обычно используем метод ap. Также, в отличие от метода chain, который позволяет отобразить только первую ошибку, метод ap позволяет собрать массив из всех исключений.</p><p>Они обычно используются в валидациях форм, в которых все ошибки, возникшие при заполнении, показываются сразу же.</p><p>Сценарий использования: У нас есть форма регистрации, в которой валидируются имя пользователя, пароль и e-mail с помощью трех функций: isUsernameValid, isPwdLengthCorrect и isEmailValid. Мы должны показать одну, две или три ошибки в зависимости от введенных данных.</p><figure><img src="https://media.tproger.ru/uploads/2016/12/1-qru9EDCwqmj-o2FgtVqAQg.png" alt="" /></figure><p>Давайте реализуем эту задачу, используя валидационный аппликативный функтор.</p><blockquote>Мы будем использовать библиотеку data.validation из folktalejs, поскольку в ramda-fantasy еще не реализованы валидации.</blockquote><p>У валидационного функтора есть два конструктора: Success и Failure, по аналогии с монадой Either.</p><p>Шаг 1: Чтобы использовать валидации, все, что нам нужно сделать — обернуть валидные значения и ошибки в Success и Failure.</p><blockquote>Проделайте это для всех полей формы.</blockquote><p>Шаг 2: Создайте функцию-заглушку.</p><p>Шаг 3: Используйте curryN, чтобы повторно применить ap.</p><p>Проблема с функцией ap в том, что левая часть выражения должна быть функтором или монадой, содержащей функцию.</p><p>Например, предположим, что мы хотим повторно применить ap, как показано ниже. Это будет работать только в том случае, когда monad1 содержит функцию. Результат monad1.ap(monad2) также должен быть монадой, содержащей функцию, чтобы мы могли использовать ap на monad3.</p><p>В нашем случае у нас есть 3 функции, которые нам надо применить</p><p>Давайте предположим, что мы сделали что-то вроде:</p><p>Код выше не сработает, потому что Success(returnSuccess).ap(isUsernameValid(username)) вернет значение, и мы не сможем вызвать от него метод ap.</p><p>Мы можем использовать curryN, чтобы возвращать функцию, пока она не вызвана N раз.</p><p>В результате мы получаем такой код:</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональное программирование с примерами на JavaScript. Часть первая. Основные техники функционального программирования</title>
      <link>https://tproger.ru/translations/functional-js-1</link>
      <comments>https://tproger.ru/translations/functional-js-1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Антон Корольков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-js-1</guid>
      <description><![CDATA[<p>Каррирование, чистые функции, fantasy-land, функторы и монады Maybe и Either — базовые техники функционального программирования с примерами кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-js-1">Функциональное программирование с примерами на JavaScript. Часть первая. Основные техники функционального программирования</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 02 Dec 2016 19:24:17 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает rajaraodv</p><p>Функциональное программирование, или ФП, может изменить стиль вашего написания программ к лучшему. Но освоить его довольно непросто, а многие посты и туториалы не рассматривают детали (вроде монад, аппликативных функторов и т.п.) и не предоставляют примеры из практики, которые помогли бы новичкам использовать мощные техники ФП ежедневно. Поэтому я решил написать статью, в которой освещу основные идеи ФП.</p><p>В первой части вы изучите основы ФП, такие как каррирование, чистые функции, fantasy-land, функторы, монады, Maybe-монады и Either-монады на нескольких примерах.</p><p>Функциональное программирование — это стиль написания программ через составление набора функций.</p><p>Основной принцип ФП — оборачивать практически все в функции, писать множество маленьких многоразовых функций, а затем просто вызывать их одну за другой, чтобы получить результат вроде (func1.func2.func3) или в композиционном стиле func1(func2(func3())).</p><p>Кроме этого, структура функций должна следовать некоторым правилам, описанным ниже.</p><p>Итак, у вас могло возникнуть несколько вопросов. Если любую задачу можно решить, объединяя вызовы нескольких функций, то:</p><ol><li>Как реализовать условия (if-else)? (Совет: используйте монаду Either);</li><li>Как перехватить исключения типа Null Exception? (В этом может помочь монада Maybe);</li><li>Как убедиться в том, что функция действительно “многоразовая” и может использоваться в любом месте? (Чистые функции);</li><li>Как убедиться, что данные, которые мы передаем, не изменяются, чтобы мы могли бы использовать их где-то еще? (Чистые функции, иммутабельность);</li><li>Если функция принимает несколько значений, но цепочка может передавать только одно значение, как мы можем сделать эту функцию частью цепочки? (Каррирование и функции высшего порядка).</li></ol><p>Чтобы решить все эти проблемы, функциональные языки, вроде Haskell, предоставляют инструменты и решения из математики, такие как монады, функторы и т.д., из коробки.</p><p>Хотя в JavaScript эти инструменты не встроены, к счастью, у него достаточно преимуществ, позволяющих людям реализовывать недостающие функции в собственных библиотеках.</p><h3>Спецификация Fantasy-Land и библиотеки ФП</h3><p>В библиотеках, содержащих такие инструменты, как функторы и монады, реализуются функции и классы, которые следуют некоторым спецификациям, чтобы предоставлять функциональность, подобную стандартной библиотеке Haskell.</p><p><a href="https://github.com/fantasyland/fantasy-land">Fantasy-Land</a> — одна из таких спецификаций, в которой описано, как должна действовать та или иная функция или класс в JS.</p><figure><img src="https://media.tproger.ru/uploads/2016/11/1-fwhU3xa-92GSWXCfUg2PKg.png" alt="" /></figure><p>На рисунке выше показаны все спецификации и их зависимости. Спецификации — это, по существу, описания функционала, подобные интерфейсам в Java. С точки зрения JS вы можете думать о спецификациях, как о классах или функциях-конструкторах, которые реализовывают некоторые методы (map, of, chain), следуя спецификации.</p><p>Например, класс в JavaScript является функтором, если он реализует метод map. Метод map должен работать, следуя спецификации.</p><p>По аналогии, класс в JS является аппликативным функтором, если он реализует функции map и ap.</p><p>JS-класс — монада, если он реализует функции, требуемые функтором, аппликативным функтором, цепочкой и самой монадой.</p><h4>Библиотеки, следующие спецификациям Fantasy-Land</h4><p>Есть несколько библиотек, следующих спецификациям FL: <a href="https://cwmyers.github.io/monet.js/">monet.js</a>, <a href="https://github.com/cullophid/barely-functional">barely-functional</a>, <a href="http://folktalejs.org/">folktalejs</a>, <a href="https://github.com/ramda/ramda-fantasy">ramda-fantasy</a>, <a href="https://github.com/DrBoolean/immutable-ext">immutable-ext</a>, <a href="https://github.com/Avaq/Fluture">Fluture</a> и т.д.</p><h4>Какие же из них мне использовать?</h4><p>Такие библиотеки, как <a href="https://github.com/lodash/lodash/wiki/FP-Guide">lodash-fp </a>и <a href="http://ramdajs.com/">ramdajs</a>, позволяют вам начать программировать в функциональном стиле. Но они не реализуют функции, позволяющие использовать ключевые математические концепты (монады, функторы, свертки), а без них невозможно решать некоторые из реальных задач в функциональном стиле.</p><p>Так что в дополнение к ним вы должны использовать одну из библиотек, следующих спецификациям FL.</p><p>Теперь, когда мы знаем основы, давайте посмотрим на несколько практических примеров и изучим на них некоторые из возможностей и техник функционального программирования.</p><h3>Пример 1: справляемся с проверкой на NULL</h3><p>Тема покрывает: функторы, монады, Maybe-монады и каррирование.</p><p>Сценарий использования: Мы хотим показать различные стартовые страницы в зависимости от языка, выбранного пользователем в настройках. В данном примере мы реализовываем функцию getUrlForUser, которая возвращает правильный URL из списка indexURLs для испанского языка, выбранного пользователем joeUser.</p><p>Проблема: язык может быть не выбран, то есть равняться null. Также сам пользователь может быть не залогинен и равняться null. Выбранный язык может быть не доступен в нашем списке indexURLs. Так что мы должны позаботиться о нескольких случаях, при которых значение null или undefined может вызвать ошибку.</p><p>Решение (Императивное против Функционального):</p><p>Не беспокойтесь, если функциональное решение пока вам не понятно, я объясню его шаг за шагом немного позже.</p><p>Давайте для начала попробуем понять некоторые из концептов ФП, которые были использованы в этом решении.</p><h4>Функторы</h4><p>Любой класс или тип данных, который хранит значение и реализует метод map, называется функтором.</p><p>Например, Array — это функтор, потому что массив хранит значения и реализует метод map, позволяющий нам применять функцию к значениям, которые он хранит.</p><p>Давайте напишем свой собственный функтор “MyFunctor”. Это просто JS-класс (функция-конструктор). Метод map применяет функцию к хранимым значениям и возвращает новый экземпляр MyFunctor.</p><p>Функторы так же должны реализовывать и другие спецификации в дополнение к методу map, но я не буду рассказывать о них в этой статье.</p><h4>Монады</h4><p>Монады — это подтип функторов, так как у них есть метод map, но они также реализуют другие методы, например, ap, of, chain.</p><p>Ниже представлена простая реализация монады.</p><p>Обычные монады используются нечасто, в отличие от более специфичных монад, таких как “монада Maybe” и “монада Either“.</p><h4>“Maybe”-монада</h4><p>Монада “Maybe” — это класс, который имплементирует спецификацию монады. Её особенность заключается в том, что с помощью нее можно решать проблемы с null и undefined.</p><p>В частности, в случае, если данные равны null или undefined, функция map пропускает их.</p><p>Код, представленный ниже, показывает имплементацию Maybe-монады в библиотеке ramda-fantasy. Она возвращает экземпляр одного из двух подклассов: Just или Nothing, в зависимости от значения.</p><p>Классы Just и Nothing содержат одинаковые методы (map, orElse и т.д.). Отличие между ними в реализации этих самых методов.</p><p>Обратите особое внимание на функции “map” и “orElse”.</p><p>Давайте поймем, как Maybe-монада осуществляет проверку на null.</p><ol><li>Если есть объект, который может равняться null или иметь нулевые свойства, создаем экземпляр монады из него.</li><li>Используем библиотеки, вроде ramdajs, чтобы получить значение из монады и работать с ним.</li><li>Возвращаем значение по умолчанию, если данные равняются null.</li></ol><h4>Каррирование</h4><p>Освещенные темы: чистые функции и композиция.</p><p>Если мы хотим создавать серии вызовов функций, как то func1.func2.func3 или (func1(func2(func3())), все эти функции должны принимать только один параметр. Например, если func2 принимает два параметра (func2(param1, param2)), мы не сможем включить её в серию.</p><p>Но с практической точки зрения многие функции могут принимать несколько параметров. Так как же нам создавать из них цепочки? Ответ: С помощью каррирования.</p><p>Каррирование превращает функцию, которая принимает несколько параметров в функцию, которая принимает только один параметр за один раз. Функция не запустится, пока все параметры не будут переданы.</p><p>В дополнение, каррирование может быть также использовано в ситуациях, когда мы обращаемся к глобальным значениям.</p><p>Давайте снова взглянем на наше решение:</p><h3>Пример 2: обработка функций, бросающих исключения и выход сразу после ошибки</h3><p>Освещенные темы: Монада “Either”</p><p>Монада Maybe подходит нам, чтобы обработать ошибки, связанные с null и undefined. Но что делать с функциями, которым требуется выбрасывать исключения? И как определить, какая из функций в цепочке вызвала ошибку, когда в серии несколько функций, бросающих исключения?</p><p>Например, если func2 из цепочки func1.func2.func3... выбросила исключение, мы должны пропустить вызов func3 и последующие функции и корректно обработать ошибку.</p><h4>Монада Either</h4><p>Монада Either превосходно подойдет для подобной ситуации.</p><p>Пример использования: В примере ниже мы рассчитываем “tax” и “discont” для “items” и в конечном счете вызываем showTotalPrice.</p><p>Заметьте, что функции “tax” и “discount” выбросят исключение, если в качестве цены передано не числовое значение. Функция “discount”, помимо этого, вернет ошибку в случае, если цена меньше 10.</p><p>Давайте посмотрим, как можно реализовать этот пример в функциональном стиле, используя монаду Either.</p><p>Either-монада предоставляет два конструктора: “Either.Left” и “Either.Right“. Думайте о них, как о подклассах Either. И “Left“, и “Right” тоже являются монадами. Идея в том, чтобы хранить ошибки или исключения в Left и полезные значения в Right.</p><p>Экземпляры Either.Left или Either.Right создаются в зависимости от значения функции.</p><p>Так давайте же посмотрим, как изменить наш императивный пример на функциональный.</p><p>Шаг 1: Оберните возвращаемые значения в Left и Right. “Оборачивание” означает создание экземпляра класса с помощью оператора new.</p><p>Шаг 2: Оберните исходное значение в Right, так как оно валидно.</p><p>Шаг 3: Создайте две функции: одну для обработки ошибок, а другую для отображения результата. Оберните их в Either.either (из библиотеки ramda-fantasy.js).</p><p>Either.either принимает 3 параметра: обработчик успешного завершения, обработчик ошибок и монаду Either. Сейчас мы можем передать только обработчики, а третий параметр, Either, передать позже.</p><p>Как только Either.either получит все три параметра, она передаст третий параметр в обработчик успешного завершения или обработчик ошибок, в зависимости от типа монады: Left или Right.</p><p>Шаг 4: Используйте метод chain, чтобы создать цепочку из нескольких функций, выбрасывающих исключения. Передайте результат их выполнения в Either.either (eitherLogOrShow).</p><p>Все вместе выглядит следующим образом:</p><p>В <a href="https://tproger.ru/translations/functional-js-2/">следующей части</a> мы рассмотрим аппликативные функторы, curryN и Validation Applicative.</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 4. Обработка исключений</title>
      <link>https://tproger.ru/translations/functional-sharp-4</link>
      <comments>https://tproger.ru/translations/functional-sharp-4?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-4</guid>
      <description><![CDATA[<p>Заключительная часть цикла о функциональном C#: обработка ошибок и исключений по мотивам концепции Railway Oriented Programming от Scott Wlaschin.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-4">Функциональный C#. Часть 4. Обработка исключений</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 03 Apr 2015 22:54:24 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы продолжаем цикл статей о функциональном C#. Сегодняшняя часть заключительная, и мы в ней рассмотрим вопрос обработки исключений и ошибок. Предлагаем вспомнить предыдущие части серии:</p><ol><li><a href="https://tproger.ru/translations/functional-sharp-1/">Функциональный C#: Неизмяемные объекты</a></li><li><a href="https://tproger.ru/translations/functional-sharp-2/">Функциональный C#: Одержимость примитивами</a></li><li><a href="https://tproger.ru/translations/functional-sharp-3/">Функциональный C#: Ненулевые ссылочные типы</a></li><li>Функциональный C#: Обработка исключений</li></ol><h3>Обработка ошибок: основной подход</h3><p>Концепция проверок и обработок исключений хорошо известна многим из вас, но код, необходимый для этого, может действительно раздражать. По крайней мере в таком языке программирования как C#. Эта статья вдохновлена концепцией Railway Oriented Programming, которую представил <a href="http://fsharpforfunandprofit.com/about/">Scott Wlaschin</a> в своей <a href="http://fsharpforfunandprofit.com/rop/">презентации</a>. Советуем вам просмотреть его полное выступление, так как это даст вам бесценные знания о том, каким неудобным может быть C# и как это обойти.</p><p>Посмотрите на пример ниже:</p><p>Кажется, в этом коде все ясно и кристально чисто. Мы создаем экземпляр класса Customer, сохраняем его в репозиторий, а затем отправляем ему поздравления по электронной почте. Но все верно ровно до тех пор, пока у нас все проходит без ошибок. Мы в этом коде предполагаем, что все функции выполнятся абсолютно точно. Но, к превеликому сожалению, такое практически невозможно.</p><p>Когда же вы начинаете отлавливать ошибки, то код становится похожим на что-то подобное:</p><p>Дела обстоят еще хуже, если при возникновении ошибки, нам потребуется отменить последнюю операцию. Тогда наш код станет еще больше:</p><p>Ну наконец-то! Теперь уж точно все. Но есть одна проблема… Наш метод ранее состоял из 5 строк, а сейчас их целых 35! Семикратное увеличение! И это только один простой метод. Более того, теперь нам трудно ориентироваться в написанном. Эти самые 5 строчек значимого кода погребены под толщей обработок исключений.</p><h3>Обработка исключений и ошибок ввода в функциональном стиле</h3><p>Можно ли обойтись без такого расширения исходного кода? К счастью, да. Давайте пройдемся по нашему методу и посмотрим, что же мы можем сделать.</p><p>Вы, возможно, заметили, что мы используем технику, описанную в одной из наших <a href="https://tproger.ru/translations/functional-c-primitive-obsession/">прошлых статей</a>. Вместо того, чтобы использовать примитивы, мы используем классы. Например, CustomerName и BillingInfo. Это позволяет ставить всю проверку на корректность в одном месте и придерживаться <a href="https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself">принципа DRY</a>.</p><p>Статический метод Create возвращает объект класса Result, который инкапсулирует всю необходимую информацию, касающуюся результатов операции — сообщение об ошибке в случае неудачи и экземпляр объекта в случае успеха.</p><p>Кроме того, обратите внимание, что функции, которые могли бы вызвать ошибки, обернуты в конструкцию try/catch. Такой подход нарушает одно из лучших практик, которая описана в <a href="http://enterprisecraftsmanship.com/2015/02/26/exceptions-for-flow-control-in-c/">этой статье</a>. Суть заключается в том, что если вы знаете, как бороться с исключением, то обработать его следует на минимально возможном уровне. Давайте перепишем код:</p><p>Как вы могли заметить, мы оборачиваем все возможные места ошибок в Result. Работа такого класса очень похожа на работу монады Maybe, о которой мы говорили в <a href="https://tproger.ru/translations/functional-c-non-nullable-reference-types/">одной из прошлых статей</a>. Используя Result, мы можем анализировать код, не глядя в детали реализации. Вот как выглядит сам класс (некоторые детали опущены для краткости):</p><p>Теперь мы можем применить тот же принцип, который используется в функциональных языках. Вот, где происходит настоящая магия:</p><p>Если вы знакомы с функциональными языками программирования, то вы, возможно, заметили, что метод OnSuccess — это, фактически, метод Bind.</p><p>Как же работает OnSuccess? Метод проверяет предыдущий результат и в случае успеха выполняет текущую операцию. В противном случае он просто возвращает последний успешный результат. Таким образом, цепь продолжается до тех пор, пока не встретится ошибка. Если же она встречается, все остальные методы попросту пропускаются.</p><p>Метод OnFailure работает с точностью до наоборот, как вы могли догадаться: выполняет текущий метод только в том случае, если предыдущая операция привела к ошибке.</p><p>OnBoth находится в конце цепочки. Используется он для вывода различного рода сообщений и логов.</p><p>Итак, мы написали нужный нам метод с обработками исключений и ошибок, но с гораздо меньшим количеством кода. Более того, обратите внимание, что теперь намного легче понимать, что вообще делает метод.</p><h3>А что насчет принципа CQS?</h3><p>CQS — Command-Query Separation — принцип императивного программирования. Он гласит, что каждый метод должен быть либо командой, которая выполняет какое-то действие, либо запросом, возвращающим данные. Есть ли у нас конфликт с данным принципом?</p><p>Нет. И более того, наш подход увеличивает читаемость кода таким же способом, что и в принципе CQS. Но теперь потенциальный диапазон информации, которую мы получаем от наших методов, расширился вдвое. Вместо 2 (значение null и какой-либо объект, возвращаемый запросом) у нас их 4:</p><p>public void Save(Customer customer)</p><p>public Customer GetById(long id)</p><p>public Result Save(Customer customer)</p><p>public Result GetById(long id)</p><p>Когда говорится, что метод не может привести к ошибке, мы не имеем в виду то, что ошибки не может быть совсем. Всегда есть какая-то вероятность, что выпадет исключение (особенно там, где его никто не ожидал). Под методами, которое не могут привести к ошибке, подразумеваются такие функции, которые должны всегда работать без исключений. То есть любая произошедшая в них ошибка является неожиданной.</p><h3>Вывод</h3><p>Очень важно смотреть на свой код и сразу понимать, что там выполняется. Код должен быть читаем. Но одновременно с этим вы должны уделять большое внимание обработке исключений. Однако классический подход довольно громоздкий и неудобный. Именно поэтому есть смысл применять технику, описанную в этой статье.</p><p>В сочетании с другими тремя техниками — <a href="https://tproger.ru/translations/functional-c-immutability/">неизменяемыми объектами</a>, <a href="https://tproger.ru/translations/functional-sharp-2/">избавлением от одержимости примитивами</a> и <a href="https://tproger.ru/translations/functional-sharp-3/">необнуляемыми ссылочными типами</a> — этот подход представляет мощную модель программирования, которая позволяет существенно повысить производительность.</p><p>Перевод статьи «Functional C#: Handling failures, input errors»</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 3. Ненулевые ссылочные типы</title>
      <link>https://tproger.ru/translations/functional-sharp-3</link>
      <comments>https://tproger.ru/translations/functional-sharp-3?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-3</guid>
      <description><![CDATA[<p>Ненулевые ссылочные типы в C# помогают избавиться от NullReferenceException, когда неизвестно, вернёт ли метод вроде GetById реальный экземпляр или null.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-3">Функциональный C#. Часть 3. Ненулевые ссылочные типы</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 30 Mar 2015 19:40:35 GMT</pubDate>
      <content:encoded><![CDATA[<p>Эта статья третья в серии “Функциональный C#”. Все части:</p><ol><li><a href="https://tproger.ru/translations/functional-sharp-1/">Функциональный C#: Неизменяемые объекты</a></li><li><a href="https://tproger.ru/translations/functional-sharp-2/">Функциональный C#: Одержимость примитивами</a></li><li>Функциональный C#: Ненулевые ссылочные типы</li><li><a href="https://tproger.ru/translations/functional-sharp-4/">Функциональный C#: Обработка исключений</a></li></ol><h3>Ненулевые ссылочные типы в C#: введение</h3><p>Посмотрите на пример, приведенный ниже:</p><p>Что-то знакомое, да? Но какие ошибки вы наблюдаете?</p><p>Проблема заключается в том, что мы не знаем наверняка, действительно ли метод GetById вернет ненулевой экземпляр. Несмотря ни на что, есть определенный шанс того, что мы получим null. В таком случае мы получим исключение NullReferenceException. Ситуация может быть еще хуже, если перед тем, как использовать переменную customer, она еще не будет инициализирована методом GetById. В этом случае нам будет очень сложно отловить такую ошибку.</p><p>Чем быстрее мы получаем обратную связь, тем меньше времени мы имеем для исправления ошибки. Конечно, максимально быстрый ответ мы можем получить только от компилятора. Согласитесь, было бы здорово написать наш код, и пусть компилятор сделает все необходимые проверки?</p><p>Где Customer! означает ненулевой тип, т.е. экземпляры такого класса не могут принимать значение null в принципе. Как думаете, было бы здорово быть уверенным в том, что компилятор укажет нам, если какой-либо кусок кода может вернуть null?</p><p>Безусловно, было бы круто! Или даже лучше:</p><p>То есть, сделать все ссылочные типы по умолчанию ненулевыми (так же, как и типы значений). И если мы захотим применить нулевой тип, то следует воспользоваться таким кодом:</p><p>Можете ли вы себе представить мир без всех этих раздражающих NullReferenceException? Я тоже не могу.</p><p>К сожалению, необнуляемые ссылочные типы не могут быть введены в C# как признак языка. Чтобы подробнее узнать об этой теме, рекомендую к прочтению эти статьи:</p><ol><li><a href="http://blog.coverity.com/2013/11/20/c-non-nullable-reference-types/#.VPYwaV19JDx">Статья Эрика Липперта</a> (Eric Lippert)</li><li><a href="http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake">Интересная, но практически нереализуемое “дизайнерское” решение</a></li></ol><p>Но не волнуйтесь. Хоть мы и не можем заставить компилятор помочь нам и использовать мощности ненулевых ссылочных типов, есть и обходные пути, к которым сейчас и прибегнем. Давайте взглянем на класс Customer, который мы написали в <a href="http://enterprisecraftsmanship.com/2015/03/07/functional-c-primitive-obsession/">прошлой статье</a>:</p><p>Как вы могли заметить, мы перенесли поля класса Customer (имя и почту) в отдельные классы. Однако, мы ничего не можем поделать с проверками на null. Это единственные условия, которые остались в классе Customer.</p><h3>Как избавиться от проверок на null</h3><p>Итак, как мы можем избавиться от таких проверок? Конечно же при помощи IL рерайтинга (Intermediate Language Rewrite)!</p><p>Есть замечательный NuGet пакет под названием <a href="https://github.com/Fody/NullGuard">NullGuard.Fody</a>. Для начала вам нужно скачать и установить пакет. Пометьте сборку таким атрибутом:</p><p>[assembly: NullGuard(ValidationFlags.All)]</p><p>Что же мы сделали? Отныне каждый метод и свойство в сборке автоматически проверяется на null. Теперь мы можем переписать класс Customer. Выглядеть он будет просто и изящно:</p><p>Или даже еще проще:</p><p>Несмотря на визуальную простоту класса, на самом деле он выглядит вот так:</p><h3>Но как быть со значением null?</h3><p>Так как же мы можем определить то, что значение некоторого типа может быть пустым (null)? Для этого мы будем использовать монаду Maybe.</p><p>Как вы можете заметить, входные значения для класса Maybe отмечены атрибутом AllowNull. Теперь мы можем написать следующий код с использованием Maybe:</p><p>И теперь становится очевидным то, что метод GetById может вернуть нулевое значение. Кроме того, теперь вы не сможете случайно перепутать значение, которое может принимать null, с необнуляемым значением, что привело бы к ошибке компилятора.</p><p>Конечно же вы теперь должны решить, какие сборки будут обработаны пакетом NullGuard.Fody. Вероятно, применение данного пакета в WPF не лучшая идея, поскольку там имеется множество системных компонентов, которые являются по своей сути обнуляемыми. Именно поэтому добавление проверок на null не даст вам особого преимущества. Однако для всех остальных сборок данный метод будет более чем актуальным.</p><p>Небольшое замечание о монаде Maybe. Вы, возможно, захотите назвать ее Option из-за соглашения об именовании языка F#. Я лично предпочитаю использовать Maybe, но по-моему, распределение программистов в этом вопросе примерно 50 на 50. Конечно, это всего лишь дело вкуса.</p><h3>Что насчет статических проверок?</h3><p>Окей, быстрая обратная связь во время выполнения это хорошо, но это все еще обратная связь во время выполнения. Было бы замечательно анализировать код еще быстрее — скажем, на этапе компиляции.</p><p>Ответ кроется в атрибуте NotNull замечательного дополнения к Visual Studio — <a href="https://www.jetbrains.com/resharper/">ReSharper</a>. Применив атрибут NotNull к какому-то методу и вернув в нем нулевое значение, вы получите предупреждение от плагина ReSharper.</p><p>Данный способ может здорово облегчить вам жизнь, однако он страдает от некоторых проблем.</p><p>Во-первых, ReSharper работает по методу от обратного. Сейчас вам нужно отмечать атрибутом NotNull те значения, которые не могут быть нулевыми. Было бы гораздо удобнее помечать этим атрибутом значения, которые, наоборот, могли бы быть нулевыми. Остальные же по умолчанию считались бы необнуляемыми.</p><p>Во-вторых, предупреждения — это всего лишь предупреждения. Вы можете запросто не обратить на них внимание и пропустить их. Разумеется, мы можем установить настройки Visual Studio так, что он будет понимать предупреждения как ошибки. Однако с монадой Maybe у вас гораздо меньше шансов ошибиться.</p><p>Именно по этим причинам я не использую ReSharper, хотя они и очень полезны.</p><h3>Вывод</h3><p>Подход, описанный выше, действительно очень мощный:</p><ol><li>Вы быстро отловите баг с нулевым значением. Теперь вас не будут надоедать постоянные NullReferenceException.</li><li>Вы увеличите читаемость кода. Теперь не будут везде мелькать постоянные проверки значения на null перед использованием объекта.</li><li>По умолчанию все ваши методы будут защищены от исключения NullReferenceException. Причем вам не надо будет помечать каждый новый метод атрибутом NotNull.</li></ol><p>А на этом все! В следующей статье мы мы рассмотрим вопрос обработки исключений в функционально C#.</p><p>Перевод статьи “Functional C#: Non-nullable reference types”</p>]]></content:encoded>
    </item>
    <item>
      <title>Новый метод проектирования микрочипов</title>
      <link>https://tproger.ru/translations/massive-chip-design-savings</link>
      <comments>https://tproger.ru/translations/massive-chip-design-savings?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/massive-chip-design-savings</guid>
      <description><![CDATA[<p>Язык программирования для проектирования аппаратуры снижает цену ошибок в дизайне чипа: один дефект в 2011 году обошёлся Intel в миллиард долларов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/massive-chip-design-savings">Новый метод проектирования микрочипов</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 28 Mar 2015 16:57:36 GMT</pubDate>
      <content:encoded><![CDATA[<p>Исследователи разработали язык программирования, который позволяет контролировать огромные расходы, связанные с проектированием аппаратных средств.</p><blockquote>«В то время как разработчик программного обеспечения может исправить ошибку программирования путем разработки и распространения патча, одного недостатка в дизайне чипа хватает, чтобы все продукты, содержащие его, были отозваны», — объясняет Кристиан Бэйдж (Christiaan Baaij), соискатель докторской степени.</blockquote><p>В 2011 году такой конструктивный дефект обошёлся Intel в целый миллиард долларов.</p><blockquote>«Очевидно, что процесс проектирования чипов, которые будут использоваться в оборудовании, требует обширного и цикличного тестирования. Это довольно дорогостоящее мероприятие, но в настоящее время — совершенно необходимое. Проектные расходы такой компании, как NVidia, составляют более миллиарда долларов в год».</blockquote><p>В своей докторской диссертации Бэйдж описывает мировое производство микрочипов на протяжении многих лет. В 1985 году Intel выпустила 386 процессор, который имел 275 000 транзисторов. Теперь, в 2015 году, самый большой чип от NVIDIA содержит более 7 000 000 000 транзисторов. Удивляют производственные показатели: всего за тридцать лет, количество транзисторов в чипе выросло с менее чем миллиона до нескольких миллиардов. К сожалению, производительность разработки не возрастает с такой же скоростью. Разработчики чипов используют одни и те же методы проектирования микросхем в течение двадцати лет.</p><h3>Функциональный язык программирования</h3><p>Разработанный функциональный язык программирования для аппаратного проектирования позволяет формально доказать правильность проектных преобразований.</p><p>Эти преобразования заставляют чип работать быстрее. Исследователи смогли доказать, что они не оказывают влияние на характеристики чипа. Это означает, что, в отличие от традиционных методов, нет необходимости проверять и перепроверять все на каждой стадии процесса проектирования. В свою очередь, это показывает, что современные языки программирования делают более управляемыми сложность — и стоимость — проектирования микросхем. Один из важных элементов этого исследования включает в себя компилятор CλaSH, который преобразует описания аппаратного обеспечения, написанные на функциональном языке Haskell, в низкоуровневое представление. Затем стандартное программное обеспечение может создать чип из этого описания. Работа Бэйджа касается усовершенствования этого компилятора, позволяющего осуществлять автоматическую генерацию аппаратного обеспечения из абстрактного описания.</p><h3>Интерес со стороны корпоративного сектора</h3><p>Департамент уже представил свою работу корпоративному сектору, который проявляет большой интерес. В день функционального программирования, который прошел в университете Твенте в Нидерландах 9 января 2015 года и был организован ученым Яном Купером (Jan Kuper), приняли участие как значительное число ученых, так и очень много представителей корпоративного сектора. Были запущены совместные проекты с Incas и Нидерландской организацией прикладных научных исследований, а Центр высокопроизводительных вычислений Штутгарта (HLRS) очень заинтересован в разработке.</p><p>Тем не менее, Бэйдж заметил, что несмотря на заинтересованность, открытие было воспринято без энтузиазма.</p><blockquote>«Многие компании недовольны тем, что им придется переучивать своих сотрудников, не говоря уже о связанных с этим проблемах . Функциональное программирование не является частью стандартной учебной программы. Но я полон надежд. Apple недавно разработала и представила мировому сообществу язык «SWIFT» — мультипарадигменный (в т.ч. функциональный) язык для разработки приложений. Зная Apple, можно быть уверенным, что в ближайшем будущем они начнут продвигать язык . Надеюсь, это значит, у нас скоро будет целая армия программистов, знакомых с функциональными языками, что позволит производителям чипов изменить технологию разработки».</blockquote><h3>Более подробная информация</h3><p>Функциональный язык программирования был разработан в отделе компьютерной архитектуры для встраиваемых систем (CAES, профессор Джерард Смит (Gerard Smit) научно-исследовательского института при Университете Твенте. Руководил исследованием Ян Купер, который преподает в бакалавриате и магистратуре по направлениям “Теория вычислительных машин и систем” и “Встраиваемые системы” соответственно.</p><p>Докторские исследования Кристиана Бэйджа в пределах этого отдела касаются компилятора, преобразующего описание на языке Haskell в описание низкого уровня. Бэйдж защитил докторскую диссертацию «Цифровые схемы в CλaSH — функциональные спецификации и типонаправленный синтез» 23 января 2015.</p><p>Перевод статьи: «Massive chip design savings to be realized»</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 2. Одержимость примитивами</title>
      <link>https://tproger.ru/translations/functional-sharp-2</link>
      <comments>https://tproger.ru/translations/functional-sharp-2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-2</guid>
      <description><![CDATA[<p>Хранение имени и почты класса Customer в примитивных типах кажется простым решением, пока не доходит до проверки значений полей на корректность.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-2">Функциональный C#. Часть 2. Одержимость примитивами</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 27 Mar 2015 22:44:56 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы продолжаем цикл статей о функциональном программировании на языке C#:</p><ol><li><a href="https://tproger.ru/translations/functional-sharp-1/">Функциональный C#: Неизменяемые объекты</a></li><li>Функциональный C#: Одержимость примитивами</li><li><a href="https://tproger.ru/translations/functional-sharp-3/">Функциональный C#: Ненулевые ссылочные типы</a></li><li><a href="https://tproger.ru/translations/functional-sharp-4/">Функциональный C#: Обработка исключений</a></li></ol><p>Представьте себе ситуацию, что вам потребовалось описать некий класс Customer, содержащий определенные данные — скажем, имя и электронную почту. К сожалению, многие программисты подумают, что использовать для имени и почты поля элементарных типов данных куда проще, чем написать базовый класс.</p><p>Если вы тоже так думаете, то вашим результатом будет подобный код:</p><p>Данный метод является правильным ровно до того момента, пока вам не понадобится проверять значения полей на корректность. Но вас так просто не убедить и вы пишете уйму проверок. В итоге класс увеличивается и обрастает различными условиями:</p><p>Да и ладно, если бы эти проверки оставались внутри класса. Но ведь они выходят на уровень выше — в основной класс приложения:</p><p>Согласитесь, такой подход, мягко говоря, не совсем корректный. А как же <a href="https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself">принцип DRY</a> и единый источник истины? В приведенном выше примере по меньшей мере 3 таких источника, что совсем не оправдано.</p><p>Именно эта ситуация и называется состоянием одержимости примитивами. В следующей главе мы покажем вам, как обойти эту “болезнь”.</p><h3>Как избавиться от одержимости примитивами?</h3><p>Очень просто! Мы всего-навсего должны ввести два новых класса, в которых мы и будем проверять значения на валидность. Это и будет единым источником истины, о котором говорилось выше.</p><p>Красота такого подхода заключается в том, что если вы захотите изменить логику проверки значений, вам нужно будет подкорректировать код ровно в одном месте. Чем меньше дублирования кода в вашей программе, тем меньше ошибок вы допустите и тем счастливее ваши клиенты!</p><p>Обратите внимание на то, что конструктор класса Email приватный. А новый экземпляр мы можем создать при помощи метода Create, который прогоняет входное значение через множество фильтров, проверяя его на валидность. Это сделано для того, чтобы значение объекта было корректным с самого начала его существования.</p><p>А вот и пример применения таких классов:</p><p>Обратите внимание, что экземпляры Result&lt;Email&gt; и Result&lt;CustomerName&gt; явно говорят нам, что метод Create может вызвать ошибку. И если это произойдет, то информацию об ошибке можно узнать из свойства Error.</p><p>А теперь давайте взглянем на класс Customer после того, как мы ввели два маленьких побочных класса:</p><p>Практически все проверки были перемещены в классы Email и CustomerName. Остались только условия с проверками на null, но их мы рассмотрим в следующей статье.</p><p>Так какие преимущества мы получили, избавившись от одержимости примитивами?</p><ol><li>Мы создали единый авторитетный источник знаний для каждого объекта и избавились от дублирования кода.</li><li>Теперь невозможно по ошибке присвоить объекту Email или CustomerName такое значение, которое привело бы к ошибке компилятора.</li><li>Нет необходимости в дополнительной проверке корректности электронной почты или имени покупателя. Если объекты класса Email или CustomerName существуют, то мы точно знаем, что данные в них хранятся абсолютно верные.</li></ol><p>Есть одна деталь, на которой бы хотелось остановиться поподробней. Дело в том, что некоторые программисты избавляются от одержимости элементарных типов не полностью. Например:</p><p>Нужно помнить, что использовать элементарные типы стоит только тогда, когда объект покидает пределы программы. То есть в тех случаях, когда значения заносятся в базу данных или экспортируются во внешний файл. Но в своем приложении старайтесь использовать написанные вами классы-обертки настолько часто, насколько это возможно. Это сделает ваш код более чистым. Убедитесь в этом сами:</p><h3>Обратная сторона: ограничения</h3><p>К сожалению, создание пользовательских типов данных в C# реализовано не так безупречно, как в функциональных языках: F#, например. Возможно, ситуацию исправит новая версия языка: C# 7.0.</p><p>Именно поэтому я считаю, что в некоторых ситуациях использование примитивов лучше, чем создание простого класса-обертки. Например, для представления денег. Они могут быть выражены при помощи элементарного типа данных с одной лишь проверкой знака числа. Да, вам придется продублировать это условие, но это решение проще, даже в долгосрочной перспективе.</p><p>Как и всегда, скажу вам, чтобы вы перед тем, как что-то написать, взвесили все “за” и “против” и только потом принимали решение. И не бойтесь менять свое мнение несколько раз.</p><h3>Вывод</h3><p>С неизменяемыми и не примитивными типами данных мы становимся все ближе и ближе к решению задач в парадигме функционального программирования. А в следующей статье мы попробуем избавиться от многочисленных проверок на null.</p><h3>Исходные коды</h3><ol><li><a href="https://gist.github.com/vkhorikov/5f0c7167250edb17cd2f">Код с одержимостью примитивами</a></li><li><a href="https://gist.github.com/vkhorikov/063953c90f5d60ddc24b">Код без одержимости примитивами</a></li></ol><p>Перевод статьи «Functional C#: Primitive obsession»</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 1. Неизменяемые объекты</title>
      <link>https://tproger.ru/translations/functional-sharp-1</link>
      <comments>https://tproger.ru/translations/functional-sharp-1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-1</guid>
      <description><![CDATA[<p>Сложность кода — главная беда корпоративных систем, и неизменяемые объекты в C# делают программу читабельнее и проще для проверки на корректность.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-1">Функциональный C#. Часть 1. Неизменяемые объекты</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Mar 2015 12:56:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы начинаем цикл статей, в которых покажем вам, как программировать на языке C# в парадигме функционального программирования. Нами будут рассмотрены темы:</p><ol><li>Функциональный C#: Неизменяемые объекты</li><li><a href="https://tproger.ru/translations/functional-sharp-2/">Функциональный C#: Одержимость примитивами</a></li><li><a href="https://tproger.ru/translations/functional-sharp-3/">Функциональный C#: Ненулевые ссылочные типы</a></li><li><a href="https://tproger.ru/translations/functional-sharp-4/">Функциональный C#: Обработка исключений</a></li></ol><h3>Почему мы рассматриваем неизменяемые объекты?</h3><p>Самой большой проблемой корпоративного программного обеспечения является сложность кода. Читабельность — это, пожалуй, один из самых важных аспектов программирования. И вы должны стремиться к тому, чтобы ваш код был лаконичен. Код, написанный без учета этого фактора, очень сложно анализировать и проверять на корректность.</p><p>Давайте рассмотрим такой пример:</p><p>Будет ли изменен объект запроса к тому моменту, когда мы выполняем второй поиск? Может быть, да. А, может быть, нет. Это зависит от того, найдем ли мы что-нибудь, осуществив первый поиск. А еще и от того, изменятся ли критерии поиска после выполнения метода AdjustSearchCriteria. Проще говоря, мы не можем знать заранее, изменится ли объект запроса во втором поиске.</p><p>А теперь рассмотрим следующий код:</p><p>Вот здесь сразу все ясно: после того, как мы ничего не нашли во время первого поиска, метод AdjustSearchCriteria создаст новые критерии, которые в свою очередь будут использоваться во втором поиске.</p><p>Итак, какие существуют проблемы в работе с подвергающимися изменениям структурами данных?</p><ol><li>Трудно оценивать написанный код, если мы не можем быть уверенными в том, изменятся ли определенные данные или нет.</li><li>Довольно сложно следовать за многочисленными отсылками, если вам потребовалось взглянуть не только на сам метод, но и на функции, которые вызываются в этом методе.</li><li>Если же вы пишете многопоточное приложение, то отслеживание и отладка кода становятся еще сложнее.</li></ol><h3>Как описать неизменяемые объекты?</h3><p>Если у вас есть относительно простой класс, то вы всегда должны рассматривать вопрос о том, чтобы сделать его неизменяемым. Это правило коррелирует с понятием Value Objects — они просты и их легко сделать неизменяемыми.</p><p>Так как же нам описать неизменяемые объекты? Давайте рассмотрим пример: у нас есть класс ProductPile, представляющий некоторые продукты, которые мы выставили на продажу:</p><p>Чтобы сделать поля класса ProductPile неизменяемыми, мы отметим их доступными только для чтения и создадим конструктор:</p><p>Итак, чего же мы добились такой организацией класса?</p><ol><li>Теперь мы можем не волноваться о корректности данных — проверять значение мы будем только один раз в конструкторе.</li><li>Мы абсолютно уверены в том, что значения объектов корректны всегда.</li><li>Объекты автоматически становятся потокобезопасными.</li><li>Увеличилась читаемость кода, поскольку теперь нет необходимости проверять, не изменились ли значения объектов.</li></ol><h3>Ограничения в использовании</h3><p>Конечно, все имеет свою цену. Мы можем применить нашу идею в маленьких и простых классах, однако она совсем не применима к большим.</p><p>Прежде всего стоит отметить производительность. Если ваш объект получается достаточно большим, то создание его копий каждый раз при изменении какого-то параметра не лучшим образом скажется на быстродействии приложения.</p><p>Хорошим примером здесь являются неизменяемые коллекции (immutable collections). Их авторы учли проблемы с производительностью и добавили класс Builder, который позволяет “мутрировать”, изменять коллекцию:</p><p>Также вы встретите множество проблем, если попытаетесь сделать изменчивый по своей природе класс неизменяемым. Но пусть вас это не останавливает.</p><h3>Вывод</h3><p>Рассмотрите все достоинства и недостатки перевода объектов класса к неизменяемым, оцените затраты и не забывайте рассуждать трезво. В большинстве случаев вы получите выгоду (разумеется, в том случае, когда ваш класс достаточно маленький и простой).</p><p>Перевод статьи «Functional C#: Immutability»</p>]]></content:encoded>
    </item>
  </channel>
</rss>